Browse Source

Merge remote-tracking branch 'upstream/master'

* upstream/master: (27 commits)
  show 'using account …' in incoming call screen
  show contact jid in call screen
  bump copyright year
  Add handling of status code 333
  increase default pw length
  do not build emoji flavors
  pulled translations from transifex
  add changelog
  fix ice candidate sending when different credentials are used
  remove security check that ensures rtp connection was properly finished
  code clean up
  bump agp
  store encrypted pgp files in private cache dir
  do not restart wakelock if activity is finishing
  delete pre lolipop weOwnFile()
  use try with resources. remove unused methods
  rename version suffix to playstore/free
  bump appcompat, migrate to emoji2 and get rid of emoji flavor
  fix rare npe
  store recordings and documents in their respective folders
  ...
for-singpolyma
Stephen Paul Weber 5 months ago
parent
commit
5e149cfcd1
No known key found for this signature in database
GPG Key ID: D11C2911CE519CDE
  1. 4
      .builds/debian-stable.yml
  2. 12
      .github/workflows/android.yml
  3. 5
      CHANGELOG.md
  4. 98
      build.gradle
  5. 11
      src/cheogram/java/eu/siacs/conversations/services/ImportBackupService.java
  6. 18
      src/compat/java/eu/siacs/conversations/ui/widget/EmojiWrapperEditText.java
  7. 47
      src/compat/java/eu/siacs/conversations/utils/EmojiWrapper.java
  8. 11
      src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java
  9. 8
      src/conversations/res/values-gl/strings.xml
  10. 14
      src/free/java/eu/siacs/conversations/services/EmojiInitializationService.java
  11. 27
      src/freeCompat/java/eu/siacs/conversations/ui/service/EmojiService.java
  12. 5
      src/main/AndroidManifest.xml
  13. 12
      src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java
  14. 4
      src/main/java/eu/siacs/conversations/entities/DownloadableFile.java
  15. 16
      src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java
  16. 583
      src/main/java/eu/siacs/conversations/persistance/FileBackend.java
  17. 2
      src/main/java/eu/siacs/conversations/services/AttachFileToConversationRunnable.java
  18. 6
      src/main/java/eu/siacs/conversations/services/ExportBackupService.java
  19. 7
      src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
  20. 13
      src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
  21. 3
      src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java
  22. 477
      src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java
  23. 114
      src/main/java/eu/siacs/conversations/ui/RecordingActivity.java
  24. 555
      src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java
  25. 2
      src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
  26. 4
      src/main/java/eu/siacs/conversations/ui/XmppActivity.java
  27. 7
      src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java
  28. 3
      src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java
  29. 2
      src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java
  30. 5
      src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
  31. 14
      src/main/java/eu/siacs/conversations/ui/util/ViewUtil.java
  32. 3
      src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java
  33. 2
      src/main/java/eu/siacs/conversations/utils/CryptoHelper.java
  34. 10
      src/main/java/eu/siacs/conversations/utils/FileWriterException.java
  35. 10
      src/main/java/eu/siacs/conversations/utils/MimeUtils.java
  36. 3
      src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java
  37. 507
      src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java
  38. 8
      src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java
  39. 1325
      src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java
  40. 150
      src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java
  41. 3
      src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/IceUdpTransportInfo.java
  42. 4
      src/main/res/layout/activity_muc_details.xml
  43. 20
      src/main/res/layout/activity_rtp_session.xml
  44. 2
      src/main/res/layout/create_conference_dialog.xml
  45. 2
      src/main/res/layout/create_public_channel_dialog.xml
  46. 4
      src/main/res/layout/dialog_quickedit.xml
  47. 3
      src/main/res/values-bg/strings.xml
  48. 3
      src/main/res/values-da-rDK/strings.xml
  49. 3
      src/main/res/values-de/strings.xml
  50. 3
      src/main/res/values-es/strings.xml
  51. 3
      src/main/res/values-fi/strings.xml
  52. 5
      src/main/res/values-gl/strings.xml
  53. 3
      src/main/res/values-it/strings.xml
  54. 3
      src/main/res/values-ja/strings.xml
  55. 5
      src/main/res/values-pl/strings.xml
  56. 3
      src/main/res/values-pt-rBR/strings.xml
  57. 3
      src/main/res/values-ro-rRO/strings.xml
  58. 3
      src/main/res/values-ru/strings.xml
  59. 3
      src/main/res/values-sv/strings.xml
  60. 3
      src/main/res/values-tr-rTR/strings.xml
  61. 3
      src/main/res/values-vi/strings.xml
  62. 3
      src/main/res/values-zh-rCN/strings.xml
  63. 2
      src/main/res/values/about.xml
  64. 10
      src/playstore/java/eu/siacs/conversations/services/EmojiInitializationService.java
  65. 55
      src/playstoreCompat/java/eu/siacs/conversations/ui/service/EmojiService.java
  66. 6
      src/playstoreCompat/res/values/font_certs.xml
  67. 14
      src/system/java/eu/siacs/conversations/ui/service/EmojiService.java
  68. 16
      src/system/java/eu/siacs/conversations/ui/widget/EmojiWrapperEditText.java
  69. 47
      src/system/java/eu/siacs/conversations/utils/EmojiWrapper.java

4
.builds/debian-stable.yml

@ -28,6 +28,6 @@ tasks:
sed -ie 's/\/\/ INSERT/implementation "io.sentry:sentry-android:5.6.1"/' build.gradle
- build: |
cd cheogram-android
./gradlew assembleCheogramFreeCompatDebug
./gradlew assembleCheogramFreeDebug
- assets: |
mv cheogram-android/build/outputs/apk/cheogramFreeCompat/debug/*.apk cheogram.apk
mv cheogram-android/build/outputs/apk/cheogramFree/debug/*.apk cheogram.apk

12
.github/workflows/android.yml

@ -22,14 +22,10 @@ jobs:
run: mkdir libs && wget -O libs/libwebrtc-m92.aar https://gultsch.de/files/libwebrtc-m92.aar
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build Quicksy (Compat)
run: ./gradlew assembleQuicksyFreeCompatDebug
- name: Build Quicksy (System)
run: ./gradlew assembleQuicksyFreeSystemDebug
- name: Build Conversations (Compat)
run: ./gradlew assembleConversationsFreeCompatDebug
- name: Build Conversations (System)
run: ./gradlew assembleConversationsFreeSystemDebug
- name: Build Quicksy
run: ./gradlew assembleQuicksyFreeDebug
- name: Build Conversations
run: ./gradlew assembleConversationsFreeDebug
- uses: actions/[email protected]
with:
name: Conversations all-flavors (debug)

5
CHANGELOG.md

@ -1,5 +1,10 @@
# Changelog
### Version 2.10.3
* Store files in location appropriate for Android 11
* Attempt to reconnect call after network switch
### Version 2.10.2
* Fix crash when rendering some quotes

98
build.gradle

@ -6,7 +6,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.1'
classpath 'com.android.tools.build:gradle:7.1.2'
}
}
@ -38,14 +38,13 @@ def urlFile = { url, name ->
configurations {
playstoreImplementation
compatImplementation
conversationsFreeCompatImplementation
cheogramFreeCompatImplementation
conversationsPlaystoreCompatImplementation
conversationsPlaystoreSystemImplementation
quicksyPlaystoreCompatImplementation
quicksyPlaystoreSystemImplementation
quicksyFreeCompatImplementation
freeImplementation
conversationsFreeImplementation
conversationsPlaystorImplementation
conversationsPlaystoreImplementation
quicksyPlaystoreImplementation
quicksyPlaystoreImplementation
quicksyFreeImplementation
quicksyImplementation
}
@ -57,22 +56,19 @@ dependencies {
exclude group: 'com.google.firebase', module: 'firebase-analytics'
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
}
conversationsPlaystoreCompatImplementation("com.android.installreferrer:installreferrer:2.2")
conversationsPlaystoreSystemImplementation("com.android.installreferrer:installreferrer:2.2")
quicksyPlaystoreCompatImplementation 'com.google.android.gms:play-services-auth-api-phone:18.0.1'
quicksyPlaystoreSystemImplementation 'com.google.android.gms:play-services-auth-api-phone:18.0.1'
conversationsPlaystoreImplementation("com.android.installreferrer:installreferrer:2.2")
quicksyPlaystoreImplementation 'com.google.android.gms:play-services-auth-api-phone:18.0.1'
implementation 'org.sufficientlysecure:openpgp-api:10.0'
implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'androidx.exifinterface:exifinterface:1.3.3'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.emoji:emoji:1.1.0'
implementation 'com.google.android.material:material:1.4.0'
compatImplementation 'androidx.emoji:emoji-appcompat:1.1.0'
conversationsFreeCompatImplementation 'androidx.emoji:emoji-bundled:1.1.0'
cheogramFreeCompatImplementation 'androidx.emoji:emoji-bundled:1.1.0'
quicksyFreeCompatImplementation 'androidx.emoji:emoji-bundled:1.1.0'
implementation "androidx.emoji2:emoji2:1.1.0-rc01"
freeImplementation "androidx.emoji2:emoji2-bundled:1.1.0-rc01"
implementation 'org.bouncycastle:bcmail-jdk15on:1.64'
//zxing stopped supporting Java 7 so we have to stick with 3.3.3
//https://github.com/zxing/zxing/issues/1170
@ -138,7 +134,7 @@ android {
targetCompatibility JavaVersion.VERSION_1_8
}
flavorDimensions("mode", "distribution", "emoji")
flavorDimensions("mode", "distribution")
productFlavors {
@ -169,45 +165,21 @@ android {
playstore {
dimension "distribution"
versionNameSuffix "+p"
versionNameSuffix "+playstore"
}
free {
dimension "distribution"
versionNameSuffix "+f"
}
system {
dimension "emoji"
versionNameSuffix "s"
}
compat {
dimension "emoji"
versionNameSuffix "c"
versionNameSuffix "+free"
}
}
sourceSets {
quicksyFreeSystem {
java {
srcDir 'src/quicksyFree/java'
}
}
quicksyFreeCompat {
quicksyFree {
java {
srcDir 'src/freeCompat/java'
srcDir 'src/quicksyFree/java'
}
}
quicksyPlaystoreCompat {
java {
srcDir 'src/playstoreCompat/java'
srcDir 'src/quicksyPlaystore/java'
}
res {
srcDir 'src/playstoreCompat/res'
srcDir 'src/quicksyPlaystore/res'
}
}
quicksyPlaystoreSystem {
quicksyPlaystore {
java {
srcDir 'src/quicksyPlaystore/java'
}
@ -215,39 +187,17 @@ android {
srcDir 'src/quicksyPlaystore/res'
}
}
conversationsFreeCompat {
conversationsFree {
java {
srcDir 'src/freeCompat/java'
srcDir 'src/conversationsFree/java'
}
}
conversationsFreeSystem {
cheogramFree {
java {
srcDir 'src/conversationsFree/java'
}
}
cheogramFreeCompat {
java {
srcDir 'src/freeCompat/java'
srcDir 'src/conversationsFree/java'
}
}
cheogramFreeSystem {
java {
srcDir 'src/conversationsFree/java'
}
}
conversationsPlaystoreCompat {
java {
srcDir 'src/playstoreCompat/java'
srcDir 'src/conversationsPlaystore/java'
}
res {
srcDir 'src/playstoreCompat/res'
srcDir 'src/conversationsPlaystore/res'
}
}
conversationsPlaystoreSystem {
conversationsPlaystore {
java {
srcDir 'src/conversationsPlaystore/java'
}
@ -262,13 +212,11 @@ android {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
versionNameSuffix "r"
}
debug {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
versionNameSuffix "d"
}
}

11
src/cheogram/java/eu/siacs/conversations/services/ImportBackupService.java

@ -128,16 +128,19 @@ public class ImportBackupService extends Service {
final List<Jid> accounts = mDatabaseBackend.getAccountJids(false);
final ArrayList<BackupFile> backupFiles = new ArrayList<>();
final Set<String> apps = new HashSet<>(Arrays.asList("Conversations", "Quicksy", getString(R.string.app_name)));
for (String app : apps) {
final File directory = new File(FileBackend.getBackupDirectory(app));
final List<File> directories = new ArrayList<>();
for (final String app : apps) {
directories.add(FileBackend.getLegacyBackupDirectory(app));
}
directories.add(FileBackend.getBackupDirectory(this));
for (final File directory : directories) {
if (!directory.exists() || !directory.isDirectory()) {
Log.d(Config.LOGTAG, "directory not found: " + directory.getAbsolutePath());
continue;
}
final File[] files = directory.listFiles();
if (files == null) {
onBackupFilesLoaded.onBackupFilesLoaded(backupFiles);
return;
continue;
}
for (final File file : files) {
if (file.isFile() && file.getName().endsWith(".ceb")) {

18
src/compat/java/eu/siacs/conversations/ui/widget/EmojiWrapperEditText.java

@ -1,18 +0,0 @@
package eu.siacs.conversations.ui.widget;
import android.content.Context;
import android.util.AttributeSet;
import androidx.emoji.widget.EmojiAppCompatEditText;
public class EmojiWrapperEditText extends EmojiAppCompatEditText {
public EmojiWrapperEditText(Context context) {
super(context);
}
public EmojiWrapperEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
}

47
src/compat/java/eu/siacs/conversations/utils/EmojiWrapper.java

@ -1,47 +0,0 @@
/*
* Copyright (c) 2017, Daniel Gultsch All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package eu.siacs.conversations.utils;
import androidx.emoji.text.EmojiCompat;
public class EmojiWrapper {
public static CharSequence transform(CharSequence input) {
try {
if (EmojiCompat.get().getLoadState() == EmojiCompat.LOAD_STATE_SUCCEEDED) {
return EmojiCompat.get().process(input);
} else {
return input;
}
} catch (IllegalStateException e) {
return input;
}
}
}

11
src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java

@ -128,16 +128,19 @@ public class ImportBackupService extends Service {
final List<Jid> accounts = mDatabaseBackend.getAccountJids(false);
final ArrayList<BackupFile> backupFiles = new ArrayList<>();
final Set<String> apps = new HashSet<>(Arrays.asList("Conversations", "Quicksy", getString(R.string.app_name)));
for (String app : apps) {
final File directory = new File(FileBackend.getBackupDirectory(app));
final List<File> directories = new ArrayList<>();
for (final String app : apps) {
directories.add(FileBackend.getLegacyBackupDirectory(app));
}
directories.add(FileBackend.getBackupDirectory(this));
for (final File directory : directories) {
if (!directory.exists() || !directory.isDirectory()) {
Log.d(Config.LOGTAG, "directory not found: " + directory.getAbsolutePath());
continue;
}
final File[] files = directory.listFiles();
if (files == null) {
onBackupFilesLoaded.onBackupFilesLoaded(backupFiles);
return;
continue;
}
for (final File file : files) {
if (file.isFile() && file.getName().endsWith(".ceb")) {

8
src/conversations/res/values-gl/strings.xml

@ -1,15 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pick_a_server">Escolle o teu provedor XMPP</string>
<string name="pick_a_server">Elixe o teu provedor XMPP</string>
<string name="use_conversations.im">Utilizar conversations.im</string>
<string name="create_new_account">Crear nova conta</string>
<string name="do_you_have_an_account">Xa posúes unha conta XMPP? Este pode ser o caso se xa estás a utilizar outro cliente XMPP ou utilizaches Conversations previamente. Se non é así podes crear unha nova conta agora mesmo.\nTruco: Algúns provedores de correo tamén proporcionan contas XMPP.</string>
<string name="server_select_text">XMPP é unha rede de mensaxería independente do provedor. Podes utilizar este cliente con calquera provedor XMPP da túa elección.\nMais para a tua conveniencia fixemos que fose doado crear unha conta en conversations.im¹; un provedor especialmente axeitado para utilizar con Conversations.</string>
<string name="magic_create_text_on_x">Convidáronte a %1$s. Guiarémoste no proceso para crear unha conta.\nAo escoller %1$s como provedor poderás comunicarte con usuarias de outros provedores cando lles deas o teu enderezo XMPP completo.</string>
<string name="magic_create_text_fixed">Convidáronte a %1$s. Escollemos un nome de usuaria por ti. Guiarémoste no proceso de crear unha conta.\nPoderás comunicarte con usuarias de outros provedores cando lles digas o teu enderezo XMPP completo.</string>
<string name="magic_create_text_on_x">Convidáronte a %1$s. Guiarémoste no proceso para crear unha conta.\nAo elexir %1$s como provedor poderás comunicarte con usuarias doutros provedores cando lles deas o teu enderezo XMPP completo.</string>
<string name="magic_create_text_fixed">Convidáronte a %1$s. Xa eleximos un nome de usuaria para ti. Guiarémoste no proceso de crear unha conta.\nPoderás comunicarte con usuarias doutros provedores cando lles digas o teu enderezo XMPP completo.</string>
<string name="your_server_invitation">O convite do teu servidor</string>
<string name="improperly_formatted_provisioning">Código de aprovisionamento con formato non válido</string>
<string name="tap_share_button_send_invite">Toca no botón compartir para convidar ó teu contacto a %1$s.</string>
<string name="tap_share_button_send_invite">Toca no botón compartir para convidar ao teu contacto a %1$s.</string>
<string name="if_contact_is_nearby_use_qr">Se o contacto está preto de ti, pode escanear o código inferior para aceptar o teu convite.</string>
<string name="easy_invite_share_text">Únete a %1$s e conversa conmigo: %2$s</string>
<string name="share_invite_with">Enviar convite a...</string>

14
src/free/java/eu/siacs/conversations/services/EmojiInitializationService.java

@ -0,0 +1,14 @@
package eu.siacs.conversations.services;
import android.content.Context;
import androidx.emoji2.bundled.BundledEmojiCompatConfig;
import androidx.emoji2.text.EmojiCompat;
public class EmojiInitializationService {
public static void execute(final Context context) {
EmojiCompat.init(new BundledEmojiCompatConfig(context).setReplaceAll(true));
}
}

27
src/freeCompat/java/eu/siacs/conversations/ui/service/EmojiService.java

@ -1,27 +0,0 @@
package eu.siacs.conversations.ui.service;
import android.content.Context;
import android.os.Build;
import androidx.emoji.text.EmojiCompat;
import androidx.emoji.text.FontRequestEmojiCompatConfig;
import androidx.emoji.bundled.BundledEmojiCompatConfig;
public class EmojiService {
private final Context context;
public EmojiService(Context context) {
this.context = context;
}
public void init() {
BundledEmojiCompatConfig config = new BundledEmojiCompatConfig(context);
//On recent Androids we assume to have the latest emojis
//there are some annoying bugs with emoji compat that make it a safer choice not to use it when possible
// a) the text preview has annoying glitches when the cut of text contains emojis (the emoji will be half visible)
// b) can trigger a hardware rendering bug https://issuetracker.google.com/issues/67102093
config.setReplaceAll(Build.VERSION.SDK_INT < Build.VERSION_CODES.O);
EmojiCompat.init(config);
}
}

5
src/main/AndroidManifest.xml

@ -50,6 +50,10 @@
android:name="android.hardware.microphone"
android:required="false" />
<queries>
<package android:name="org.sufficientlysecure.keychain"/>
</queries>
<application
android:allowBackup="true"
@ -61,6 +65,7 @@
android:largeHeap="true"
android:networkSecurityConfig="@xml/network_security_configuration"
android:requestLegacyExternalStorage="true"
android:preserveLegacyExternalStorage="true"
android:theme="@style/ConversationsTheme"
tools:replace="android:label"
tools:targetApi="q">

12
src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java

@ -9,6 +9,7 @@ import org.openintents.openpgp.util.OpenPgpApi;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
@ -147,9 +148,6 @@ public class PgpDecryptionService {
try {
os.flush();
final String body = os.toString();
if (body == null) {
throw new IOException("body was null");
}
message.setBody(body);
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager();
@ -194,9 +192,9 @@ public class PgpDecryptionService {
String originalExtension = originalFilename == null ? null : MimeUtils.extractRelevantExtension(originalFilename);
if (originalExtension != null && MimeUtils.extractRelevantExtension(outputFile.getName()) == null) {
Log.d(Config.LOGTAG,"detected original filename during pgp decryption");
String mime = MimeUtils.guessMimeTypeFromExtension(originalExtension);
String path = outputFile.getName()+"."+originalExtension;
DownloadableFile fixedFile = mXmppConnectionService.getFileBackend().getFileForPath(path,mime);
final String mime = MimeUtils.guessMimeTypeFromExtension(originalExtension);
final String filename = outputFile.getName()+"."+originalExtension;
final File fixedFile = mXmppConnectionService.getFileBackend().getStorageLocation(filename,mime);
if (fixedFile.getParentFile().mkdirs()) {
Log.d(Config.LOGTAG,"created parent directories for "+fixedFile.getAbsolutePath());
}
@ -205,7 +203,7 @@ public class PgpDecryptionService {
}
if (outputFile.renameTo(fixedFile)) {
Log.d(Config.LOGTAG, "renamed " + outputFile.getAbsolutePath() + " to " + fixedFile.getAbsolutePath());
message.setRelativeFilePath(path);
message.setRelativeFilePath(fixedFile.getAbsolutePath());
}
}
final String url = message.getFileParams().url;

4
src/main/java/eu/siacs/conversations/entities/DownloadableFile.java

@ -16,6 +16,10 @@ public class DownloadableFile extends File {
private byte[] aeskey;
private byte[] iv;
public DownloadableFile(final File parent, final String file) {
super(parent, file);
}
public DownloadableFile(String path) {
super(path);
}

16
src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java

@ -96,11 +96,8 @@ public class HttpDownloadConnection implements Transferable {
this.message.setEncryption(Message.ENCRYPTION_NONE);
}
final String ext = extension.getExtension();
if (ext != null) {
message.setRelativeFilePath(String.format("%s.%s", message.getUuid(), ext));
} else if (Strings.isNullOrEmpty(message.getRelativeFilePath())) {
message.setRelativeFilePath(message.getUuid());
}
final String filename = Strings.isNullOrEmpty(ext) ? message.getUuid() : String.format("%s.%s", message.getUuid(), ext);
mXmppConnectionService.getFileBackend().setupRelativeFilePath(message, filename);
setupFile();
if (this.message.getEncryption() == Message.ENCRYPTION_AXOLOTL && this.file.getKey() == null) {
this.message.setEncryption(Message.ENCRYPTION_NONE);
@ -122,7 +119,7 @@ public class HttpDownloadConnection implements Transferable {
private void setupFile() {
final String reference = mUrl.fragment();
if (reference != null && AesGcmURL.IV_KEY.matcher(reference).matches()) {
this.file = new DownloadableFile(mXmppConnectionService.getCacheDir().getAbsolutePath() + "/" + message.getUuid());
this.file = new DownloadableFile(mXmppConnectionService.getCacheDir(), message.getUuid());
this.file.setKeyAndIv(CryptoHelper.hexToBytes(reference));
Log.d(Config.LOGTAG, "create temporary OMEMO encrypted file: " + this.file.getAbsolutePath() + "(" + message.getMimeType() + ")");
} else {
@ -326,7 +323,7 @@ public class HttpDownloadConnection implements Transferable {
if (Strings.isNullOrEmpty(extension.getExtension()) && contentType != null) {
final String fileExtension = MimeUtils.guessExtensionFromMimeType(contentType);
if (fileExtension != null) {
message.setRelativeFilePath(String.format("%s.%s", message.getUuid(), fileExtension));
mXmppConnectionService.getFileBackend().setupRelativeFilePath(message, String.format("%s.%s", message.getUuid(), fileExtension), contentType);
Log.d(Config.LOGTAG, "rewriting name after not finding extension in url but in content type");
setupFile();
}
@ -419,8 +416,9 @@ public class HttpDownloadConnection implements Transferable {
Log.d(Config.LOGTAG, "content-length reported on GET (" + size + ") did not match Content-Length reported on HEAD (" + expected + ")");
}
file.getParentFile().mkdirs();
Log.d(Config.LOGTAG,"creating file: "+file.getAbsolutePath());
if (!file.exists() && !file.createNewFile()) {
throw new FileWriterException();
throw new FileWriterException(file);
}
outputStream = AbstractConnectionManager.createOutputStream(file, false, false);
}
@ -431,7 +429,7 @@ public class HttpDownloadConnection implements Transferable {
try {
outputStream.write(buffer, 0, count);
} catch (IOException e) {
throw new FileWriterException();
throw new FileWriterException(file);
}
updateProgress(Math.round(((double) transmitted / expected) * 100));
}

583
src/main/java/eu/siacs/conversations/persistance/FileBackend.java

File diff suppressed because it is too large Load Diff

2
src/main/java/eu/siacs/conversations/services/AttachFileToConversationRunnable.java

@ -91,7 +91,7 @@ public class AttachFileToConversationRunnable implements Runnable, TranscoderLis
private void processAsVideo() throws FileNotFoundException {
Log.d(Config.LOGTAG, "processing file as video");
mXmppConnectionService.startForcingForegroundNotification();
message.setRelativeFilePath(message.getUuid() + ".mp4");
mXmppConnectionService.getFileBackend().setupRelativeFilePath(message, String.format("%s.%s", message.getUuid(), "mp4"));
final DownloadableFile file = mXmppConnectionService.getFileBackend().getFile(message);
if (Objects.requireNonNull(file.getParentFile()).mkdirs()) {
Log.d(Config.LOGTAG, "created parent directory for video file");

6
src/main/java/eu/siacs/conversations/services/ExportBackupService.java

@ -291,7 +291,7 @@ public class ExportBackupService extends Service {
secureRandom.nextBytes(salt);
final BackupFileHeader backupFileHeader = new BackupFileHeader(getString(R.string.app_name), account.getJid(), System.currentTimeMillis(), IV, salt);
final Progress progress = new Progress(mBuilder, max, count);
final File file = new File(FileBackend.getBackupDirectory(this) + account.getJid().asBareJid().toEscapedString() + ".ceb");
final File file = new File(FileBackend.getBackupDirectory(this), account.getJid().asBareJid().toEscapedString() + ".ceb");
files.add(file);
final File directory = file.getParentFile();
if (directory != null && directory.mkdirs()) {
@ -335,7 +335,7 @@ public class ExportBackupService extends Service {
}
private void notifySuccess(final List<File> files) {
final String path = FileBackend.getBackupDirectory(this);
final String path = FileBackend.getBackupDirectory(this).getAbsolutePath();
PendingIntent openFolderIntent = null;
@ -363,7 +363,7 @@ public class ExportBackupService extends Service {
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup");
mBuilder.setContentTitle(getString(R.string.notification_backup_created_title))
.setContentText(getString(R.string.notification_backup_created_subtitle, path))
.setStyle(new NotificationCompat.BigTextStyle().bigText(getString(R.string.notification_backup_created_subtitle, FileBackend.getBackupDirectory(this))))
.setStyle(new NotificationCompat.BigTextStyle().bigText(getString(R.string.notification_backup_created_subtitle, FileBackend.getBackupDirectory(this).getAbsolutePath())))
.setAutoCancel(true)
.setContentIntent(openFolderIntent)
.setSmallIcon(R.drawable.ic_archive_white_24dp);

7
src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java

@ -46,7 +46,6 @@ import eu.siacs.conversations.ui.util.MyLinkify;
import eu.siacs.conversations.ui.util.SoftKeyboardUtils;
import eu.siacs.conversations.utils.AccountUtils;
import eu.siacs.conversations.utils.Compatibility;
import eu.siacs.conversations.utils.EmojiWrapper;
import eu.siacs.conversations.utils.StringUtils;
import eu.siacs.conversations.utils.StylingHelper;
import eu.siacs.conversations.utils.XmppUri;
@ -471,11 +470,11 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
String subject = mucOptions.getSubject();
final boolean hasTitle;
if (printableValue(roomName)) {
this.binding.mucTitle.setText(EmojiWrapper.transform(roomName));
this.binding.mucTitle.setText(roomName);
this.binding.mucTitle.setVisibility(View.VISIBLE);
hasTitle = true;
} else if (!printableValue(subject)) {
this.binding.mucTitle.setText(EmojiWrapper.transform(mConversation.getName()));
this.binding.mucTitle.setText(mConversation.getName());
hasTitle = true;
this.binding.mucTitle.setVisibility(View.VISIBLE);
} else {
@ -486,7 +485,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
SpannableStringBuilder spannable = new SpannableStringBuilder(subject);
StylingHelper.format(spannable, this.binding.mucSubject.getCurrentTextColor());
MyLinkify.addLinks(spannable, false);
this.binding.mucSubject.setText(EmojiWrapper.transform(spannable));
this.binding.mucSubject.setText(spannable);
this.binding.mucSubject.setTextAppearance(this, subject.length() > (hasTitle ? 128 : 196) ? R.style.TextAppearance_Conversations_Body1_Linkified : R.style.TextAppearance_Conversations_Subhead);
this.binding.mucSubject.setAutoLinkMask(0);
this.binding.mucSubject.setVisibility(View.VISIBLE);

13
src/main/java/eu/siacs/conversations/ui/ConversationFragment.java

@ -6,6 +6,7 @@ import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@ -1183,8 +1184,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
cancelTransmission.setVisible(true);
}
if (m.isFileOrImage() && !deleted && !cancelable) {
String path = m.getRelativeFilePath();
if (path == null || !path.startsWith("/") || FileBackend.isInDirectoryThatShouldNotBeScanned(getActivity(), path)) {
final String path = m.getRelativeFilePath();
if (path == null || !path.startsWith("/")) {
deleteFile.setVisible(true);
deleteFile.setTitle(activity.getString(R.string.delete_x_file, UIHelper.getFileDescriptionString(activity, m)));
}
@ -1744,7 +1745,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
if (context == null) {
return;
}
if (intent.resolveActivity(context.getPackageManager()) != null) {
try {
if (chooser) {
startActivityForResult(
Intent.createChooser(intent, getString(R.string.perform_action_with)),
@ -1752,7 +1753,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
} else {
startActivityForResult(intent, attachmentChoice);
}
} else {
} catch (final ActivityNotFoundException e) {
Toast.makeText(context, R.string.no_application_found, Toast.LENGTH_LONG).show();
}
}
@ -2254,10 +2255,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
private List<Uri> cleanUris(final List<Uri> uris) {
Iterator<Uri> iterator = uris.iterator();
final Iterator<Uri> iterator = uris.iterator();
while (iterator.hasNext()) {
final Uri uri = iterator.next();
if (FileBackend.weOwnFile(getActivity(), uri)) {
if (FileBackend.weOwnFile(uri)) {
iterator.remove();
Toast.makeText(getActivity(), R.string.security_violation_not_attaching_file, Toast.LENGTH_SHORT).show();
}

3
src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java

@ -81,7 +81,6 @@ import eu.siacs.conversations.ui.util.ActivityResult;
import eu.siacs.conversations.ui.util.ConversationMenuConfigurator;
import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
import eu.siacs.conversations.ui.util.PendingItem;
import eu.siacs.conversations.utils.EmojiWrapper;
import eu.siacs.conversations.utils.ExceptionHelper;
import eu.siacs.conversations.utils.SignupUtils;
import eu.siacs.conversations.utils.XmppUri;
@ -615,7 +614,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
if (mainFragment instanceof ConversationFragment) {
final Conversation conversation = ((ConversationFragment) mainFragment).getConversation();
if (conversation != null) {
actionBar.setTitle(EmojiWrapper.transform(conversation.getName()));
actionBar.setTitle(conversation.getName());
actionBar.setDisplayHomeAsUpEnabled(true);
ActionBarUtil.setActionBarOnClickListener(
binding.toolbar,

477
src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java

@ -22,6 +22,7 @@ import java.util.List;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.EnterJidDialogBinding;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
import eu.siacs.conversations.ui.interfaces.OnBackendConnected;
import eu.siacs.conversations.ui.util.DelayedHintHelper;
@ -29,234 +30,250 @@ import eu.siacs.conversations.xmpp.Jid;
public class EnterJidDialog extends DialogFragment implements OnBackendConnected, TextWatcher {
private static final List<String> SUSPICIOUS_DOMAINS = Arrays.asList("conference","muc","room","rooms","chat");
private OnEnterJidDialogPositiveListener mListener = null;
private static final String TITLE_KEY = "title";
private static final String POSITIVE_BUTTON_KEY = "positive_button";
private static final String PREFILLED_JID_KEY = "prefilled_jid";
private static final String ACCOUNT_KEY = "account";
private static final String ALLOW_EDIT_JID_KEY = "allow_edit_jid";
private static final String ACCOUNTS_LIST_KEY = "activated_accounts_list";
private static final String SANITY_CHECK_JID = "sanity_check_jid";
private KnownHostsAdapter knownHostsAdapter;
private Collection<String> whitelistedDomains = Collections.emptyList();
private EnterJidDialogBinding binding;
private AlertDialog dialog;
private boolean sanityCheckJid = false;
private boolean issuedWarning = false;
public static EnterJidDialog newInstance(final List<String> activatedAccounts,
final String title, final String positiveButton,
final String prefilledJid, final String account,
boolean allowEditJid, final boolean sanity_check_jid) {
EnterJidDialog dialog = new EnterJidDialog();
Bundle bundle = new Bundle();
bundle.putString(TITLE_KEY, title);
bundle.putString(POSITIVE_BUTTON_KEY, positiveButton);
bundle.putString(PREFILLED_JID_KEY, prefilledJid);
bundle.putString(ACCOUNT_KEY, account);
bundle.putBoolean(ALLOW_EDIT_JID_KEY, allowEditJid);
bundle.putStringArrayList(ACCOUNTS_LIST_KEY, (ArrayList<String>) activatedAccounts);
bundle.putBoolean(SANITY_CHECK_JID, sanity_check_jid);
dialog.setArguments(bundle);
return dialog;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setRetainInstance(true);
}
@Override
public void onStart() {
super.onStart();
final Activity activity = getActivity();
if (activity instanceof XmppActivity && ((XmppActivity) activity).xmppConnectionService != null) {
refreshKnownHosts();
}
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(getArguments().getString(TITLE_KEY));
binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.enter_jid_dialog, null, false);
this.knownHostsAdapter = new KnownHostsAdapter(getActivity(), R.layout.simple_list_item);
binding.jid.setAdapter(this.knownHostsAdapter);
binding.jid.addTextChangedListener(this);
String prefilledJid = getArguments().getString(PREFILLED_JID_KEY);
if (prefilledJid != null) {
binding.jid.append(prefilledJid);
if (!getArguments().getBoolean(ALLOW_EDIT_JID_KEY)) {
binding.jid.setFocusable(false);
binding.jid.setFocusableInTouchMode(false);
binding.jid.setClickable(false);
binding.jid.setCursorVisible(false);
}
}
sanityCheckJid = getArguments().getBoolean(SANITY_CHECK_JID, false);
DelayedHintHelper.setHint(R.string.account_settings_example_jabber_id, binding.jid);
String account = getArguments().getString(ACCOUNT_KEY);
if (account == null) {
StartConversationActivity.populateAccountSpinner(getActivity(), getArguments().getStringArrayList(ACCOUNTS_LIST_KEY), binding.account);
} else {
ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(),
R.layout.simple_list_item,
new String[]{account});
binding.account.setEnabled(false);
adapter.setDropDownViewResource(R.layout.simple_list_item);
binding.account.setAdapter(adapter);
}
builder.setView(binding.getRoot());
builder.setNegativeButton(R.string.cancel, null);
builder.setPositiveButton(getArguments().getString(POSITIVE_BUTTON_KEY), null);
this.dialog = builder.create();
View.OnClickListener dialogOnClick = v -> {
handleEnter(binding, account);
};
binding.jid.setOnEditorActionListener((v, actionId, event) -> {
handleEnter(binding, account);
return true;
});
dialog.show();
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(dialogOnClick);
return dialog;
}
private void handleEnter(EnterJidDialogBinding binding, String account) {
final Jid accountJid;
if (!binding.account.isEnabled() && account == null) {
return;
}
try {
if (Config.DOMAIN_LOCK != null) {
accountJid = Jid.ofEscaped((String) binding.account.getSelectedItem(), Config.DOMAIN_LOCK, null);
} else {
accountJid = Jid.ofEscaped((String) binding.account.getSelectedItem());
}
} catch (final IllegalArgumentException e) {
return;
}
final Jid contactJid;
try {
contactJid = Jid.ofEscaped(binding.jid.getText().toString());
} catch (final IllegalArgumentException e) {
binding.jidLayout.setError(getActivity().getString(R.string.invalid_jid));
return;
}
if (!issuedWarning && sanityCheckJid) {
if (contactJid.isDomainJid()) {
binding.jidLayout.setError(getActivity().getString(R.string.this_looks_like_a_domain));
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.add_anway);
issuedWarning = true;
return;
}
if (suspiciousSubDomain(contactJid.getDomain().toEscapedString())) {
binding.jidLayout.setError(getActivity().getString(R.string.this_looks_like_channel));
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.add_anway);
issuedWarning = true;
return;
}
}
if (mListener != null) {
try {
if (mListener.onEnterJidDialogPositive(accountJid, contactJid)) {
dialog.dismiss();
}
} catch (JidError error) {
binding.jidLayout.setError(error.toString());
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.add);
issuedWarning = false;
}
}
}
public void setOnEnterJidDialogPositiveListener(OnEnterJidDialogPositiveListener listener) {
this.mListener = listener;
}
@Override
public void onBackendConnected() {
refreshKnownHosts();
}
private void refreshKnownHosts() {
Activity activity = getActivity();
if (activity instanceof XmppActivity) {
Collection<String> hosts = ((XmppActivity) activity).xmppConnectionService.getKnownHosts();
this.knownHostsAdapter.refresh(hosts);
this.whitelistedDomains = hosts;
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
if (issuedWarning) {
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.add);
binding.jidLayout.setError(null);
issuedWarning = false;
}
}
public interface OnEnterJidDialogPositiveListener {
boolean onEnterJidDialogPositive(Jid account, Jid contact) throws EnterJidDialog.JidError;
}
public static class JidError extends Exception {
final String msg;
public JidError(final String msg) {
this.msg = msg;
}
public String toString() {
return msg;
}
}
@Override
public void onDestroyView() {
Dialog dialog = getDialog();
if (dialog != null && getRetainInstance()) {
dialog.setDismissMessage(null);
}
super.onDestroyView();
}
private boolean suspiciousSubDomain(String domain) {
if (this.whitelistedDomains.contains(domain)) {
return false;
}
final String[] parts = domain.split("\\.");
return parts.length >= 3 && SUSPICIOUS_DOMAINS.contains(parts[0]);
}
private static final List<String> SUSPICIOUS_DOMAINS =
Arrays.asList("conference", "muc", "room", "rooms", "chat");
private OnEnterJidDialogPositiveListener mListener = null;
private static final String TITLE_KEY = "title";
private static final String POSITIVE_BUTTON_KEY = "positive_button";
private static final String PREFILLED_JID_KEY = "prefilled_jid";
private static final String ACCOUNT_KEY = "account";
private static final String ALLOW_EDIT_JID_KEY = "allow_edit_jid";
private static final String ACCOUNTS_LIST_KEY = "activated_accounts_list";
private static final String SANITY_CHECK_JID = "sanity_check_jid";
private KnownHostsAdapter knownHostsAdapter;
private Collection<String> whitelistedDomains = Collections.emptyList();
private EnterJidDialogBinding binding;
private AlertDialog dialog;
private boolean sanityCheckJid = false;
private boolean issuedWarning = false;
public static EnterJidDialog newInstance(
final List<String> activatedAccounts,
final String title,
final String positiveButton,
final String prefilledJid,
final String account,
boolean allowEditJid,
final boolean sanity_check_jid) {
EnterJidDialog dialog = new EnterJidDialog();
Bundle bundle = new Bundle();
bundle.putString(TITLE_KEY, title);
bundle.putString(POSITIVE_BUTTON_KEY, positiveButton);
bundle.putString(PREFILLED_JID_KEY, prefilledJid);
bundle.putString(ACCOUNT_KEY, account);
bundle.putBoolean(ALLOW_EDIT_JID_KEY, allowEditJid);
bundle.putStringArrayList(ACCOUNTS_LIST_KEY, (ArrayList<String>) activatedAccounts);
bundle.putBoolean(SANITY_CHECK_JID, sanity_check_jid);
dialog.setArguments(bundle);
return dialog;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
setRetainInstance(true);
}
@Override
public void onStart() {
super.onStart();
final Activity activity = getActivity();
if (activity instanceof XmppActivity
&& ((XmppActivity) activity).xmppConnectionService != null) {
refreshKnownHosts();
}
}
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(getArguments().getString(TITLE_KEY));
binding =
DataBindingUtil.inflate(
getActivity().getLayoutInflater(), R.layout.enter_jid_dialog, null, false);
this.knownHostsAdapter = new KnownHostsAdapter(getActivity(), R.layout.simple_list_item);
binding.jid.setAdapter(this.knownHostsAdapter);
binding.jid.addTextChangedListener(this);
String prefilledJid = getArguments().getString(PREFILLED_JID_KEY);
if (prefilledJid != null) {
binding.jid.append(prefilledJid);
if (!getArguments().getBoolean(ALLOW_EDIT_JID_KEY)) {
binding.jid.setFocusable(false);
binding.jid.setFocusableInTouchMode(false);
binding.jid.setClickable(false);
binding.jid.setCursorVisible(false);
}
}
sanityCheckJid = getArguments().getBoolean(SANITY_CHECK_JID, false);
DelayedHintHelper.setHint(R.string.account_settings_example_jabber_id, binding.jid);
String account = getArguments().getString(ACCOUNT_KEY);
if (account == null) {
StartConversationActivity.populateAccountSpinner(
getActivity(),
getArguments().getStringArrayList(ACCOUNTS_LIST_KEY),
binding.account);
} else {
ArrayAdapter<String> adapter =
new ArrayAdapter<>(
getActivity(), R.layout.simple_list_item, new String[] {account});
binding.account.setEnabled(false);
adapter.setDropDownViewResource(R.layout.simple_list_item);
binding.account.setAdapter(adapter);
}
builder.setView(binding.getRoot());
builder.setNegativeButton(R.string.cancel, null);
builder.setPositiveButton(getArguments().getString(POSITIVE_BUTTON_KEY), null);
this.dialog = builder.create();
View.OnClickListener dialogOnClick =
v -> {
handleEnter(binding, account);
};
binding.jid.setOnEditorActionListener(
(v, actionId, event) -> {
handleEnter(binding, account);
return true;
});
dialog.show();
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(dialogOnClick);
return dialog;
}
private void handleEnter(EnterJidDialogBinding binding, String account) {
final Jid accountJid;
if (!binding.account.isEnabled() && account == null) {
return;
}
try {
if (Config.DOMAIN_LOCK != null) {
accountJid =
Jid.ofEscaped(
(String) binding.account.getSelectedItem(),
Config.DOMAIN_LOCK,
null);
} else {
accountJid = Jid.ofEscaped((String) binding.account.getSelectedItem());
}
} catch (final IllegalArgumentException e) {
return;
}
final Jid contactJid;
try {
contactJid = Jid.ofEscaped(binding.jid.getText().toString());
} catch (final IllegalArgumentException e) {
binding.jidLayout.setError(getActivity().getString(R.string.invalid_jid));
return;
}
if (!issuedWarning && sanityCheckJid) {
if (contactJid.isDomainJid()) {
binding.jidLayout.setError(
getActivity().getString(R.string.this_looks_like_a_domain));
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.add_anway);
issuedWarning = true;
return;
}
if (suspiciousSubDomain(contactJid.getDomain().toEscapedString())) {
binding.jidLayout.setError(
getActivity().getString(R.string.this_looks_like_channel));
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.add_anway);
issuedWarning = true;
return;
}
}
if (mListener != null) {
try {
if (mListener.onEnterJidDialogPositive(accountJid, contactJid)) {
dialog.dismiss();
}
} catch (JidError error) {
binding.jidLayout.setError(error.toString());
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.add);
issuedWarning = false;
}
}
}
public void setOnEnterJidDialogPositiveListener(OnEnterJidDialogPositiveListener listener) {
this.mListener = listener;
}
@Override
public void onBackendConnected() {
refreshKnownHosts();
}
private void refreshKnownHosts() {
final Activity activity = getActivity();
if (activity instanceof XmppActivity) {
final XmppConnectionService service = ((XmppActivity) activity).xmppConnectionService;
if (service == null) {
return;
}
final Collection<String> hosts = service.getKnownHosts();
this.knownHostsAdapter.refresh(hosts);
this.whitelistedDomains = hosts;
}
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
if (issuedWarning) {
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.add);
binding.jidLayout.setError(null);
issuedWarning = false;
}
}
public interface OnEnterJidDialogPositiveListener {
boolean onEnterJidDialogPositive(Jid account, Jid contact) throws EnterJidDialog.JidError;
}
public static class JidError extends Exception {
final String msg;
public JidError(final String msg) {
this.msg = msg;
}
@NonNull
public String toString() {
return msg;
}
}
@Override
public void onDestroyView() {
Dialog dialog = getDialog();
if (dialog != null && getRetainInstance()) {
dialog.setDismissMessage(null);
}
super.onDestroyView();
}
private boolean suspiciousSubDomain(String domain) {
if (this.whitelistedDomains.contains(domain)) {
return false;
}
final String[] parts = domain.split("\\.");
return parts.length >= 3 && SUSPICIOUS_DOMAINS.contains(parts[0]);
}
}

114
src/main/java/eu/siacs/conversations/ui/RecordingActivity.java

@ -1,11 +1,12 @@
package eu.siacs.conversations.ui;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.media.MediaRecorder;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.FileObserver;
import android.os.Handler;
import android.os.SystemClock;
@ -17,25 +18,22 @@ import android.widget.Toast;
import androidx.databinding.DataBindingUtil;
import java.io.File;