Detailed changes
@@ -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
@@ -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/upload-artifact@v2
with:
name: Conversations all-flavors (debug)
@@ -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
@@ -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"
}
}
@@ -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")) {
@@ -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);
- }
-
-}
@@ -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;
- }
- }
-}
@@ -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")) {
@@ -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>
@@ -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));
+ }
+
+}
@@ -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);
- }
-
-}
@@ -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">
@@ -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;
@@ -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);
}
@@ -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));
}
@@ -1,6 +1,5 @@
package eu.siacs.conversations.persistance;
-import android.annotation.TargetApi;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
@@ -33,6 +32,7 @@ import androidx.annotation.StringRes;
import androidx.core.content.FileProvider;
import androidx.exifinterface.media.ExifInterface;
+import com.google.common.base.Strings;
import com.google.common.io.ByteStreams;
import java.io.ByteArrayOutputStream;
@@ -63,7 +63,7 @@ import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.AttachFileToConversationRunnable;
import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.ui.RecordingActivity;
+import eu.siacs.conversations.ui.adapter.MediaAdapter;
import eu.siacs.conversations.ui.util.Attachment;
import eu.siacs.conversations.utils.Compatibility;
import eu.siacs.conversations.utils.CryptoHelper;
@@ -76,7 +76,8 @@ public class FileBackend {
private static final Object THUMBNAIL_LOCK = new Object();
- private static final SimpleDateFormat IMAGE_DATE_FORMAT = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US);
+ private static final SimpleDateFormat IMAGE_DATE_FORMAT =
+ new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US);
private static final String FILE_PROVIDER = ".files";
private static final float IGNORE_PADDING = 0.15f;
@@ -86,19 +87,6 @@ public class FileBackend {
this.mXmppConnectionService = service;
}
- private static boolean isInDirectoryThatShouldNotBeScanned(Context context, File file) {
- return isInDirectoryThatShouldNotBeScanned(context, file.getAbsolutePath());
- }
-
- public static boolean isInDirectoryThatShouldNotBeScanned(Context context, String path) {
- for (String type : new String[]{RecordingActivity.STORAGE_DIRECTORY_TYPE_NAME, "Files"}) {
- if (path.startsWith(getConversationsDirectory(context, type))) {
- return true;
- }
- }
- return false;
- }
-
public static long getFileSize(Context context, Uri uri) {
try {
final Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
@@ -114,11 +102,14 @@ public class FileBackend {
}
}
- public static boolean allFilesUnderSize(Context context, List<Attachment> attachments, long max) {
- final boolean compressVideo = !AttachFileToConversationRunnable.getVideoCompression(context).equals("uncompressed");
+ public static boolean allFilesUnderSize(
+ Context context, List<Attachment> attachments, long max) {
+ final boolean compressVideo =
+ !AttachFileToConversationRunnable.getVideoCompression(context)
+ .equals("uncompressed");
if (max <= 0) {
Log.d(Config.LOGTAG, "server did not report max file size for http upload");
- return true; //exception to be compatible with HTTP Upload < v0.2
+ return true; // exception to be compatible with HTTP Upload < v0.2
}
for (Attachment attachment : attachments) {
if (attachment.getType() != Attachment.Type.FILE) {
@@ -127,41 +118,42 @@ public class FileBackend {
String mime = attachment.getMime();
if (mime != null && mime.startsWith("video/") && compressVideo) {
try {
- Dimensions dimensions = FileBackend.getVideoDimensions(context, attachment.getUri());
+ Dimensions dimensions =
+ FileBackend.getVideoDimensions(context, attachment.getUri());
if (dimensions.getMin() > 720) {
- Log.d(Config.LOGTAG, "do not consider video file with min width larger than 720 for size check");
+ Log.d(
+ Config.LOGTAG,
+ "do not consider video file with min width larger than 720 for size check");
continue;
}
} catch (NotAVideoFile notAVideoFile) {
- //ignore and fall through
+ // ignore and fall through
}
}
if (FileBackend.getFileSize(context, attachment.getUri()) > max) {
- Log.d(Config.LOGTAG, "not all files are under " + max + " bytes. suggesting falling back to jingle");
+ Log.d(
+ Config.LOGTAG,
+ "not all files are under "
+ + max
+ + " bytes. suggesting falling back to jingle");
return false;
}
}
return true;
}
- public static String getConversationsDirectory(Context context, final String type) {
- if (Config.ONLY_INTERNAL_STORAGE) {
- return context.getFilesDir().getAbsolutePath() + "/" + type + "/";
- } else {
- return getAppMediaDirectory(context) + context.getString(R.string.app_name) + " " + type + "/";
- }
- }
-
- public static String getAppMediaDirectory(Context context) {
- return Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + context.getString(R.string.app_name) + "/Media/";
+ public static File getBackupDirectory(final Context context) {
+ final File conversationsDownloadDirectory =
+ new File(
+ Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_DOWNLOADS),
+ context.getString(R.string.app_name));
+ return new File(conversationsDownloadDirectory, "Backup");
}
- public static String getBackupDirectory(Context context) {
- return getBackupDirectory(context.getString(R.string.app_name));
- }
-
- public static String getBackupDirectory(String app) {
- return Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + app + "/Backup/";
+ public static File getLegacyBackupDirectory(final String app) {
+ final File appDirectory = new File(Environment.getExternalStorageDirectory(), app);
+ return new File(appDirectory, "Backup");
}
private static Bitmap rotate(final Bitmap bitmap, final int degree) {
@@ -180,7 +172,8 @@ public class FileBackend {
}
public static boolean isPathBlacklisted(String path) {
- final String androidDataPath = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/";
+ final String androidDataPath =
+ Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android/data/";
return path.startsWith(androidDataPath);
}
@@ -192,10 +185,6 @@ public class FileBackend {
return paint;
}
- private static String getTakePhotoPath() {
- return Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) + "/Camera/";
- }
-
public static Uri getUriForUri(Context context, Uri uri) {
if ("file".equals(uri.getScheme())) {
return getUriForFile(context, new File(uri.getPath()));
@@ -246,7 +235,6 @@ public class FileBackend {
return calcSampleSize(options, size);
}
-
private static int calcSampleSize(BitmapFactory.Options options, int size) {
int height = options.outHeight;
int width = options.outWidth;
@@ -256,8 +244,7 @@ public class FileBackend {
int halfHeight = height / 2;
int halfWidth = width / 2;
- while ((halfHeight / inSampleSize) > size
- && (halfWidth / inSampleSize) > size) {
+ while ((halfHeight / inSampleSize) > size && (halfWidth / inSampleSize) > size) {
inSampleSize *= 2;
}
}
@@ -274,7 +261,8 @@ public class FileBackend {
return getVideoDimensions(mediaMetadataRetriever);
}
- private static Dimensions getVideoDimensionsOfFrame(MediaMetadataRetriever mediaMetadataRetriever) {
+ private static Dimensions getVideoDimensionsOfFrame(
+ MediaMetadataRetriever mediaMetadataRetriever) {
Bitmap bitmap = null;
try {
bitmap = mediaMetadataRetriever.getFrameAtTime();
@@ -288,8 +276,10 @@ public class FileBackend {
}
}
- private static Dimensions getVideoDimensions(MediaMetadataRetriever metadataRetriever) throws NotAVideoFile {
- String hasVideo = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
+ private static Dimensions getVideoDimensions(MediaMetadataRetriever metadataRetriever)
+ throws NotAVideoFile {
+ String hasVideo =
+ metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_HAS_VIDEO);
if (hasVideo == null) {
throw new NotAVideoFile();
}
@@ -301,14 +291,18 @@ public class FileBackend {
boolean rotated = rotation == 90 || rotation == 270;
int height;
try {
- String h = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
+ String h =
+ metadataRetriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT);
height = Integer.parseInt(h);
} catch (Exception e) {
height = -1;
}
int width;
try {
- String w = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
+ String w =
+ metadataRetriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH);
width = Integer.parseInt(w);
} catch (Exception e) {
width = -1;
@@ -319,7 +313,9 @@ public class FileBackend {
}
private static int extractRotationFromMediaRetriever(MediaMetadataRetriever metadataRetriever) {
- String r = metadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
+ String r =
+ metadataRetriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
try {
return Integer.parseInt(r);
} catch (Exception e) {
@@ -357,36 +353,20 @@ public class FileBackend {
}
}
- public static boolean weOwnFile(Context context, Uri uri) {
+ public static boolean weOwnFile(final Uri uri) {
if (uri == null || !ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {
return false;
- } else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
- return fileIsInFilesDir(context, uri);
} else {
return weOwnFileLollipop(uri);
}
}
- /**
- * This is more than hacky but probably way better than doing nothing
- * Further 'optimizations' might contain to get the parents of CacheDir and NoBackupDir
- * and check against those as well
- */
- private static boolean fileIsInFilesDir(Context context, Uri uri) {
- try {
- final String haystack = context.getFilesDir().getParentFile().getCanonicalPath();
- final String needle = new File(uri.getPath()).getCanonicalPath();
- return needle.startsWith(haystack);
- } catch (IOException e) {
- return false;
- }
- }
-
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
- private static boolean weOwnFileLollipop(Uri uri) {
+ private static boolean weOwnFileLollipop(final Uri uri) {
try {
File file = new File(uri.getPath());
- FileDescriptor fd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY).getFileDescriptor();
+ FileDescriptor fd =
+ ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
+ .getFileDescriptor();
StructStat st = Os.fstat(fd);
return st.st_uid == android.os.Process.myUid();
} catch (FileNotFoundException e) {
@@ -400,18 +380,22 @@ public class FileBackend {
final String filePath = file.getAbsolutePath();
final Cursor cursor;
try {
- cursor = context.getContentResolver().query(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
- new String[]{MediaStore.Images.Media._ID},
- MediaStore.Images.Media.DATA + "=? ",
- new String[]{filePath}, null);
+ cursor =
+ context.getContentResolver()
+ .query(
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ new String[] {MediaStore.Images.Media._ID},
+ MediaStore.Images.Media.DATA + "=? ",
+ new String[] {filePath},
+ null);
} catch (SecurityException e) {
return null;
}
if (cursor != null && cursor.moveToFirst()) {
final int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
cursor.close();
- return Uri.withAppendedPath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, String.valueOf(id));
+ return Uri.withAppendedPath(
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI, String.valueOf(id));
} else {
return null;
}
@@ -433,15 +417,30 @@ public class FileBackend {
final String mime = attachment.getMime();
if ("application/pdf".equals(mime) && Compatibility.runsTwentyOne()) {
bitmap = cropCenterSquarePdf(attachment.getUri(), size);
- drawOverlay(bitmap, paintOverlayBlackPdf(bitmap) ? R.drawable.open_pdf_black : R.drawable.open_pdf_white, 0.75f);
+ drawOverlay(
+ bitmap,
+ paintOverlayBlackPdf(bitmap)
+ ? R.drawable.open_pdf_black
+ : R.drawable.open_pdf_white,
+ 0.75f);
} else if (mime != null && mime.startsWith("video/")) {
bitmap = cropCenterSquareVideo(attachment.getUri(), size);
- drawOverlay(bitmap, paintOverlayBlack(bitmap) ? R.drawable.play_video_black : R.drawable.play_video_white, 0.75f);
+ drawOverlay(
+ bitmap,
+ paintOverlayBlack(bitmap)
+ ? R.drawable.play_video_black
+ : R.drawable.play_video_white,
+ 0.75f);
} else {
bitmap = cropCenterSquare(attachment.getUri(), size);
if (bitmap != null && "image/gif".equals(mime)) {
Bitmap withGifOverlay = bitmap.copy(Bitmap.Config.ARGB_8888, true);
- drawOverlay(withGifOverlay, paintOverlayBlack(withGifOverlay) ? R.drawable.play_gif_black : R.drawable.play_gif_white, 1.0f);
+ drawOverlay(
+ withGifOverlay,
+ paintOverlayBlack(withGifOverlay)
+ ? R.drawable.play_gif_black
+ : R.drawable.play_gif_white,
+ 1.0f);
bitmap.recycle();
bitmap = withGifOverlay;
}
@@ -452,53 +451,31 @@ public class FileBackend {
return bitmap;
}
- private void createNoMedia(File diretory) {
- final File noMedia = new File(diretory, ".nomedia");
- if (!noMedia.exists()) {
- try {
- if (!noMedia.createNewFile()) {
- Log.d(Config.LOGTAG, "created nomedia file " + noMedia.getAbsolutePath());
- }
- } catch (Exception e) {
- Log.d(Config.LOGTAG, "could not create nomedia file");
- }
- }
- }
-
public void updateMediaScanner(File file) {
updateMediaScanner(file, null);
}
public void updateMediaScanner(File file, final Runnable callback) {
- if (!isInDirectoryThatShouldNotBeScanned(mXmppConnectionService, file)) {
- MediaScannerConnection.scanFile(mXmppConnectionService, new String[]{file.getAbsolutePath()}, null, new MediaScannerConnection.MediaScannerConnectionClient() {
- @Override
- public void onMediaScannerConnected() {
-
- }
-
- @Override
- public void onScanCompleted(String path, Uri uri) {
- if (callback != null && file.getAbsolutePath().equals(path)) {
- callback.run();
- } else {
- Log.d(Config.LOGTAG, "media scanner scanned wrong file");
- if (callback != null) {
+ MediaScannerConnection.scanFile(
+ mXmppConnectionService,
+ new String[] {file.getAbsolutePath()},
+ null,
+ new MediaScannerConnection.MediaScannerConnectionClient() {
+ @Override
+ public void onMediaScannerConnected() {}
+
+ @Override
+ public void onScanCompleted(String path, Uri uri) {
+ if (callback != null && file.getAbsolutePath().equals(path)) {
callback.run();
+ } else {
+ Log.d(Config.LOGTAG, "media scanner scanned wrong file");
+ if (callback != null) {
+ callback.run();
+ }
}
}
- }
- });
- return;
- /*Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
- intent.setData(Uri.fromFile(file));
- mXmppConnectionService.sendBroadcast(intent);*/
- } else if (file.getAbsolutePath().startsWith(getAppMediaDirectory(mXmppConnectionService))) {
- createNoMedia(file.getParentFile());
- }
- if (callback != null) {
- callback.run();
- }
+ });
}
public boolean deleteFile(Message message) {
@@ -515,25 +492,30 @@ public class FileBackend {
return getFile(message, true);
}
-
public DownloadableFile getFileForPath(String path) {
- return getFileForPath(path, MimeUtils.guessMimeTypeFromExtension(MimeUtils.extractRelevantExtension(path)));
+ return getFileForPath(
+ path,
+ MimeUtils.guessMimeTypeFromExtension(MimeUtils.extractRelevantExtension(path)));
}
- public DownloadableFile getFileForPath(String path, String mime) {
- final DownloadableFile file;
+ private DownloadableFile getFileForPath(final String path, final String mime) {
if (path.startsWith("/")) {
- file = new DownloadableFile(path);
+ return new DownloadableFile(path);
} else {
- if (mime != null && mime.startsWith("image/")) {
- file = new DownloadableFile(getConversationsDirectory("Images") + path);
- } else if (mime != null && mime.startsWith("video/")) {
- file = new DownloadableFile(getConversationsDirectory("Videos") + path);
- } else {
- file = new DownloadableFile(getConversationsDirectory("Files") + path);
- }
+ return getLegacyFileForFilename(path, mime);
+ }
+ }
+
+ public DownloadableFile getLegacyFileForFilename(final String filename, final String mime) {
+ if (Strings.isNullOrEmpty(mime)) {
+ return new DownloadableFile(getLegacyStorageLocation("Files"), filename);
+ } else if (mime.startsWith("image/")) {
+ return new DownloadableFile(getLegacyStorageLocation("Images"), filename);
+ } else if (mime.startsWith("video/")) {
+ return new DownloadableFile(getLegacyStorageLocation("Videos"), filename);
+ } else {
+ return new DownloadableFile(getLegacyStorageLocation("Files"), filename);
}
- return file;
}
public boolean isInternalFile(final File file) {
@@ -542,33 +524,50 @@ public class FileBackend {
}
public DownloadableFile getFile(Message message, boolean decrypted) {
- final boolean encrypted = !decrypted
- && (message.getEncryption() == Message.ENCRYPTION_PGP
- || message.getEncryption() == Message.ENCRYPTION_DECRYPTED);
+ final boolean encrypted =
+ !decrypted
+ && (message.getEncryption() == Message.ENCRYPTION_PGP
+ || message.getEncryption() == Message.ENCRYPTION_DECRYPTED);
String path = message.getRelativeFilePath();
if (path == null) {
path = message.getUuid();
}
final DownloadableFile file = getFileForPath(path, message.getMimeType());
if (encrypted) {
- return new DownloadableFile(getConversationsDirectory("Files") + file.getName() + ".pgp");
+ return new DownloadableFile(
+ mXmppConnectionService.getCacheDir(),
+ String.format("%s.%s", file.getName(), "pgp"));
} else {
return file;
}
}
public List<Attachment> convertToAttachments(List<DatabaseBackend.FilePath> relativeFilePaths) {
- List<Attachment> attachments = new ArrayList<>();
- for (DatabaseBackend.FilePath relativeFilePath : relativeFilePaths) {
- final String mime = MimeUtils.guessMimeTypeFromExtension(MimeUtils.extractRelevantExtension(relativeFilePath.path));
+ final List<Attachment> attachments = new ArrayList<>();
+ for (final DatabaseBackend.FilePath relativeFilePath : relativeFilePaths) {
+ final String mime =
+ MimeUtils.guessMimeTypeFromExtension(
+ MimeUtils.extractRelevantExtension(relativeFilePath.path));
final File file = getFileForPath(relativeFilePath.path, mime);
attachments.add(Attachment.of(relativeFilePath.uuid, file, mime));
}
return attachments;
}
- private String getConversationsDirectory(final String type) {
- return getConversationsDirectory(mXmppConnectionService, type);
+ private File getLegacyStorageLocation(final String type) {
+ if (Config.ONLY_INTERNAL_STORAGE) {
+ return new File(mXmppConnectionService.getFilesDir(), type);
+ } else {
+ final File appDirectory =
+ new File(
+ Environment.getExternalStorageDirectory(),
+ mXmppConnectionService.getString(R.string.app_name));
+ final File appMediaDirectory = new File(appDirectory, "Media");
+ final String locationName =
+ String.format(
+ "%s %s", mXmppConnectionService.getString(R.string.app_name), type);
+ return new File(appMediaDirectory, locationName);
+ }
}
private Bitmap resize(final Bitmap originalBitmap, int size) throws IOException {
@@ -586,7 +585,8 @@ public class FileBackend {
scalledW = size;
scalledH = Math.max((int) (h / ((double) w / size)), 1);
}
- final Bitmap result = Bitmap.createScaledBitmap(originalBitmap, scalledW, scalledH, true);
+ final Bitmap result =
+ Bitmap.createScaledBitmap(originalBitmap, scalledW, scalledH, true);
if (!originalBitmap.isRecycled()) {
originalBitmap.recycle();
}
@@ -603,19 +603,26 @@ public class FileBackend {
}
final File file = new File(path);
long size = file.length();
- if (size == 0 || size >= mXmppConnectionService.getResources().getInteger(R.integer.auto_accept_filesize)) {
+ if (size == 0
+ || size
+ >= mXmppConnectionService
+ .getResources()
+ .getInteger(R.integer.auto_accept_filesize)) {
return false;
}
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
try {
- final InputStream inputStream = mXmppConnectionService.getContentResolver().openInputStream(uri);
+ final InputStream inputStream =
+ mXmppConnectionService.getContentResolver().openInputStream(uri);
BitmapFactory.decodeStream(inputStream, null, options);
close(inputStream);
if (options.outMimeType == null || options.outHeight <= 0 || options.outWidth <= 0) {
return false;
}
- return (options.outWidth <= Config.IMAGE_SIZE && options.outHeight <= Config.IMAGE_SIZE && options.outMimeType.contains(Config.IMAGE_FORMAT.name().toLowerCase()));
+ return (options.outWidth <= Config.IMAGE_SIZE
+ && options.outHeight <= Config.IMAGE_SIZE
+ && options.outMimeType.contains(Config.IMAGE_FORMAT.name().toLowerCase()));
} catch (FileNotFoundException e) {
Log.d(Config.LOGTAG, "unable to get image dimensions", e);
return false;
@@ -627,7 +634,9 @@ public class FileBackend {
}
private void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException {
- Log.d(Config.LOGTAG, "copy file (" + uri.toString() + ") to private storage " + file.getAbsolutePath());
+ Log.d(
+ Config.LOGTAG,
+ "copy file (" + uri.toString() + ") to private storage " + file.getAbsolutePath());
file.getParentFile().mkdirs();
try {
file.createNewFile();
@@ -635,19 +644,20 @@ public class FileBackend {
throw new FileCopyException(R.string.error_unable_to_create_temporary_file);
}
try (final OutputStream os = new FileOutputStream(file);
- final InputStream is = mXmppConnectionService.getContentResolver().openInputStream(uri)) {
+ final InputStream is =
+ mXmppConnectionService.getContentResolver().openInputStream(uri)) {
if (is == null) {
throw new FileCopyException(R.string.error_file_not_found);
}
try {
ByteStreams.copy(is, os);
} catch (IOException e) {
- throw new FileWriterException();
+ throw new FileWriterException(file);
}
try {
os.flush();
} catch (IOException e) {
- throw new FileWriterException();
+ throw new FileWriterException(file);
}
} catch (final FileNotFoundException e) {
cleanup(file);
@@ -664,7 +674,8 @@ public class FileBackend {
}
}
- public void copyFileToPrivateStorage(Message message, Uri uri, String type) throws FileCopyException {
+ public void copyFileToPrivateStorage(Message message, Uri uri, String type)
+ throws FileCopyException {
String mime = MimeUtils.guessMimeTypeFromUriAndMime(mXmppConnectionService, uri, type);
Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage (mime=" + mime + ")");
String extension = MimeUtils.guessExtensionFromMimeType(mime);
@@ -675,29 +686,22 @@ public class FileBackend {
if ("ogg".equals(extension) && type != null && type.startsWith("audio/")) {
extension = "oga";
}
- message.setRelativeFilePath(message.getUuid() + "." + extension);
+ setupRelativeFilePath(message, String.format("%s.%s", message.getUuid(), extension));
copyFileToPrivateStorage(mXmppConnectionService.getFileBackend().getFile(message), uri);
}
- private String getExtensionFromUri(Uri uri) {
- String[] projection = {MediaStore.MediaColumns.DATA};
+ private String getExtensionFromUri(final Uri uri) {
+ final String[] projection = {MediaStore.MediaColumns.DATA};
String filename = null;
- Cursor cursor;
- try {
- cursor = mXmppConnectionService.getContentResolver().query(uri, projection, null, null, null);
- } catch (IllegalArgumentException e) {
- cursor = null;
- }
- if (cursor != null) {
- try {
- if (cursor.moveToFirst()) {
- filename = cursor.getString(0);
- }
- } catch (Exception e) {
- filename = null;
- } finally {
- cursor.close();
+ try (final Cursor cursor =
+ mXmppConnectionService
+ .getContentResolver()
+ .query(uri, projection, null, null, null)) {
+ if (cursor != null && cursor.moveToFirst()) {
+ filename = cursor.getString(0);
}
+ } catch (final SecurityException | IllegalArgumentException e) {
+ filename = null;
}
if (filename == null) {
final List<String> segments = uri.getPathSegments();
@@ -705,11 +709,12 @@ public class FileBackend {
filename = segments.get(segments.size() - 1);
}
}
- int pos = filename == null ? -1 : filename.lastIndexOf('.');
+ final int pos = filename == null ? -1 : filename.lastIndexOf('.');
return pos > 0 ? filename.substring(pos + 1) : null;
}
- private void copyImageToPrivateStorage(File file, Uri image, int sampleSize) throws FileCopyException, ImageCompressionException {
+ private void copyImageToPrivateStorage(File file, Uri image, int sampleSize)
+ throws FileCopyException, ImageCompressionException {
final File parent = file.getParentFile();
if (parent != null && parent.mkdirs()) {
Log.d(Config.LOGTAG, "created parent directory");
@@ -743,7 +748,10 @@ public class FileBackend {
scaledBitmap = rotate(scaledBitmap, rotation);
boolean targetSizeReached = false;
int quality = Config.IMAGE_QUALITY;
- final int imageMaxSize = mXmppConnectionService.getResources().getInteger(R.integer.auto_accept_filesize);
+ final int imageMaxSize =
+ mXmppConnectionService
+ .getResources()
+ .getInteger(R.integer.auto_accept_filesize);
while (!targetSizeReached) {
os = new FileOutputStream(file);
Log.d(Config.LOGTAG, "compressing image with quality " + quality);
@@ -788,32 +796,79 @@ public class FileBackend {
}
}
- public void copyImageToPrivateStorage(File file, Uri image) throws FileCopyException, ImageCompressionException {
- Log.d(Config.LOGTAG, "copy image (" + image.toString() + ") to private storage " + file.getAbsolutePath());
+ public void copyImageToPrivateStorage(File file, Uri image)
+ throws FileCopyException, ImageCompressionException {
+ Log.d(
+ Config.LOGTAG,
+ "copy image ("
+ + image.toString()
+ + ") to private storage "
+ + file.getAbsolutePath());
copyImageToPrivateStorage(file, image, 0);
}
- public void copyImageToPrivateStorage(Message message, Uri image) throws FileCopyException, ImageCompressionException {
+ public void copyImageToPrivateStorage(Message message, Uri image)
+ throws FileCopyException, ImageCompressionException {
+ final String filename;
switch (Config.IMAGE_FORMAT) {
case JPEG:
- message.setRelativeFilePath(message.getUuid() + ".jpg");
+ filename = String.format("%s.%s", message.getUuid(), "jpg");
break;
case PNG:
- message.setRelativeFilePath(message.getUuid() + ".png");
+ filename = String.format("%s.%s", message.getUuid(), "png");
break;
case WEBP:
- message.setRelativeFilePath(message.getUuid() + ".webp");
+ filename = String.format("%s.%s", message.getUuid(), "webp");
break;
+ default:
+ throw new IllegalStateException("Unknown image format");
}
+ setupRelativeFilePath(message, filename);
copyImageToPrivateStorage(getFile(message), image);
updateFileParams(message);
}
+ public void setupRelativeFilePath(final Message message, final String filename) {
+ final String extension = MimeUtils.extractRelevantExtension(filename);
+ final String mime = MimeUtils.guessMimeTypeFromExtension(extension);
+ setupRelativeFilePath(message, filename, mime);
+ }
+
+ public File getStorageLocation(final String filename, final String mime) {
+ final File parentDirectory;
+ if (Strings.isNullOrEmpty(mime)) {
+ parentDirectory =
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
+ } else if (mime.startsWith("image/")) {
+ parentDirectory =
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
+ } else if (mime.startsWith("video/")) {
+ parentDirectory =
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES);
+ } else if (MediaAdapter.DOCUMENT_MIMES.contains(mime)) {
+ parentDirectory =
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOCUMENTS);
+ } else {
+ parentDirectory =
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
+ }
+ final File appDirectory =
+ new File(parentDirectory, mXmppConnectionService.getString(R.string.app_name));
+ return new File(appDirectory, filename);
+ }
+
+ public void setupRelativeFilePath(
+ final Message message, final String filename, final String mime) {
+ final File file = getStorageLocation(filename, mime);
+ message.setRelativeFilePath(file.getAbsolutePath());
+ }
+
public boolean unusualBounds(final Uri image) {
try {
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
- final InputStream inputStream = mXmppConnectionService.getContentResolver().openInputStream(image);
+ final InputStream inputStream =
+ mXmppConnectionService.getContentResolver().openInputStream(image);
BitmapFactory.decodeStream(inputStream, null, options);
close(inputStream);
float ratio = (float) options.outHeight / options.outWidth;
@@ -833,7 +888,8 @@ public class FileBackend {
}
private int getRotation(final Uri image) {
- try (final InputStream is = mXmppConnectionService.getContentResolver().openInputStream(image)) {
+ try (final InputStream is =
+ mXmppConnectionService.getContentResolver().openInputStream(image)) {
return is == null ? 0 : getRotation(is);
} catch (final Exception e) {
return 0;
@@ -842,7 +898,9 @@ public class FileBackend {
private static int getRotation(final InputStream inputStream) throws IOException {
final ExifInterface exif = new ExifInterface(inputStream);
- final int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
+ final int orientation =
+ exif.getAttributeInt(
+ ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
switch (orientation) {
case ExifInterface.ORIENTATION_ROTATE_180:
return 180;
@@ -880,7 +938,12 @@ public class FileBackend {
thumbnail = rotate(thumbnail, getRotation(file));
if (mime.equals("image/gif")) {
Bitmap withGifOverlay = thumbnail.copy(Bitmap.Config.ARGB_8888, true);
- drawOverlay(withGifOverlay, paintOverlayBlack(withGifOverlay) ? R.drawable.play_gif_black : R.drawable.play_gif_white, 1.0f);
+ drawOverlay(
+ withGifOverlay,
+ paintOverlayBlack(withGifOverlay)
+ ? R.drawable.play_gif_black
+ : R.drawable.play_gif_white,
+ 1.0f);
thumbnail.recycle();
thumbnail = withGifOverlay;
}
@@ -903,27 +966,36 @@ public class FileBackend {
}
private void drawOverlay(Bitmap bitmap, int resource, float factor) {
- Bitmap overlay = BitmapFactory.decodeResource(mXmppConnectionService.getResources(), resource);
+ Bitmap overlay =
+ BitmapFactory.decodeResource(mXmppConnectionService.getResources(), resource);
Canvas canvas = new Canvas(bitmap);
float targetSize = Math.min(canvas.getWidth(), canvas.getHeight()) * factor;
- Log.d(Config.LOGTAG, "target size overlay: " + targetSize + " overlay bitmap size was " + overlay.getHeight());
+ Log.d(
+ Config.LOGTAG,
+ "target size overlay: "
+ + targetSize
+ + " overlay bitmap size was "
+ + overlay.getHeight());
float left = (canvas.getWidth() - targetSize) / 2.0f;
float top = (canvas.getHeight() - targetSize) / 2.0f;
RectF dst = new RectF(left, top, left + targetSize - 1, top + targetSize - 1);
canvas.drawBitmap(overlay, null, dst, createAntiAliasingPaint());
}
- /**
- * https://stackoverflow.com/a/3943023/210897
- */
+ /** https://stackoverflow.com/a/3943023/210897 */
private boolean paintOverlayBlack(final Bitmap bitmap) {
final int h = bitmap.getHeight();
final int w = bitmap.getWidth();
int record = 0;
for (int y = Math.round(h * IGNORE_PADDING); y < h - Math.round(h * IGNORE_PADDING); ++y) {
- for (int x = Math.round(w * IGNORE_PADDING); x < w - Math.round(w * IGNORE_PADDING); ++x) {
+ for (int x = Math.round(w * IGNORE_PADDING);
+ x < w - Math.round(w * IGNORE_PADDING);
+ ++x) {
int pixel = bitmap.getPixel(x, y);
- if ((Color.red(pixel) * 0.299 + Color.green(pixel) * 0.587 + Color.blue(pixel) * 0.114) > 186) {
+ if ((Color.red(pixel) * 0.299
+ + Color.green(pixel) * 0.587
+ + Color.blue(pixel) * 0.114)
+ > 186) {
--record;
} else {
++record;
@@ -940,7 +1012,10 @@ public class FileBackend {
for (int y = 0; y < h; ++y) {
for (int x = 0; x < w; ++x) {
int pixel = bitmap.getPixel(x, y);
- if ((Color.red(pixel) * 0.299 + Color.green(pixel) * 0.587 + Color.blue(pixel) * 0.114) > 186) {
+ if ((Color.red(pixel) * 0.299
+ + Color.green(pixel) * 0.587
+ + Color.blue(pixel) * 0.114)
+ > 186) {
white++;
}
}
@@ -975,16 +1050,27 @@ public class FileBackend {
frame = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
frame.eraseColor(0xff000000);
}
- drawOverlay(frame, paintOverlayBlack(frame) ? R.drawable.play_video_black : R.drawable.play_video_white, 0.75f);
+ drawOverlay(
+ frame,
+ paintOverlayBlack(frame)
+ ? R.drawable.play_video_black
+ : R.drawable.play_video_white,
+ 0.75f);
return frame;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private Bitmap getPdfDocumentPreview(final File file, final int size) {
try {
- final ParcelFileDescriptor fileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
+ final ParcelFileDescriptor fileDescriptor =
+ ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
final Bitmap rendered = renderPdfDocument(fileDescriptor, size, true);
- drawOverlay(rendered, paintOverlayBlackPdf(rendered) ? R.drawable.open_pdf_black : R.drawable.open_pdf_white, 0.75f);
+ drawOverlay(
+ rendered,
+ paintOverlayBlackPdf(rendered)
+ ? R.drawable.open_pdf_black
+ : R.drawable.open_pdf_white,
+ 0.75f);
return rendered;
} catch (final IOException | SecurityException e) {
Log.d(Config.LOGTAG, "unable to render PDF document preview", e);
@@ -994,11 +1080,11 @@ public class FileBackend {
}
}
-
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private Bitmap cropCenterSquarePdf(final Uri uri, final int size) {
try {
- ParcelFileDescriptor fileDescriptor = mXmppConnectionService.getContentResolver().openFileDescriptor(uri, "r");
+ ParcelFileDescriptor fileDescriptor =
+ mXmppConnectionService.getContentResolver().openFileDescriptor(uri, "r");
final Bitmap bitmap = renderPdfDocument(fileDescriptor, size, false);
return cropCenterSquare(bitmap, size);
} catch (Exception e) {
@@ -1009,11 +1095,15 @@ public class FileBackend {
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
- private Bitmap renderPdfDocument(ParcelFileDescriptor fileDescriptor, int targetSize, boolean fit) throws IOException {
+ private Bitmap renderPdfDocument(
+ ParcelFileDescriptor fileDescriptor, int targetSize, boolean fit) throws IOException {
final PdfRenderer pdfRenderer = new PdfRenderer(fileDescriptor);
final PdfRenderer.Page page = pdfRenderer.openPage(0);
- final Dimensions dimensions = scalePdfDimensions(new Dimensions(page.getHeight(), page.getWidth()), targetSize, fit);
- final Bitmap rendered = Bitmap.createBitmap(dimensions.width, dimensions.height, Bitmap.Config.ARGB_8888);
+ final Dimensions dimensions =
+ scalePdfDimensions(
+ new Dimensions(page.getHeight(), page.getWidth()), targetSize, fit);
+ final Bitmap rendered =
+ Bitmap.createBitmap(dimensions.width, dimensions.height, Bitmap.Config.ARGB_8888);
rendered.eraseColor(0xffffffff);
page.render(rendered, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
page.close();
@@ -1023,12 +1113,19 @@ public class FileBackend {
}
public Uri getTakePhotoUri() {
- File file;
+ final String filename =
+ String.format("IMG_%s.%s", IMAGE_DATE_FORMAT.format(new Date()), "jpg");
+ final File directory;
if (Config.ONLY_INTERNAL_STORAGE) {
- file = new File(mXmppConnectionService.getCacheDir().getAbsolutePath(), "Camera/IMG_" + IMAGE_DATE_FORMAT.format(new Date()) + ".jpg");
+ directory = new File(mXmppConnectionService.getCacheDir(), "Camera");
} else {
- file = new File(getTakePhotoPath() + "IMG_" + IMAGE_DATE_FORMAT.format(new Date()) + ".jpg");
+ directory =
+ new File(
+ Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_DCIM),
+ "Camera");
}
+ final File file = new File(directory, filename);
file.getParentFile().mkdirs();
return getUriForFile(mXmppConnectionService, file);
}
@@ -1036,11 +1133,15 @@ public class FileBackend {
public Avatar getPepAvatar(Uri image, int size, Bitmap.CompressFormat format) {
final Avatar uncompressAvatar = getUncompressedAvatar(image);
- if (uncompressAvatar != null && uncompressAvatar.image.length() <= Config.AVATAR_CHAR_LIMIT) {
+ if (uncompressAvatar != null
+ && uncompressAvatar.image.length() <= Config.AVATAR_CHAR_LIMIT) {
return uncompressAvatar;
}
if (uncompressAvatar != null) {
- Log.d(Config.LOGTAG, "uncompressed avatar exceeded char limit by " + (uncompressAvatar.image.length() - Config.AVATAR_CHAR_LIMIT));
+ Log.d(
+ Config.LOGTAG,
+ "uncompressed avatar exceeded char limit by "
+ + (uncompressAvatar.image.length() - Config.AVATAR_CHAR_LIMIT));
}
Bitmap bm = cropCenterSquare(image, size);
@@ -1059,7 +1160,9 @@ public class FileBackend {
private Avatar getUncompressedAvatar(Uri uri) {
Bitmap bitmap = null;
try {
- bitmap = BitmapFactory.decodeStream(mXmppConnectionService.getContentResolver().openInputStream(uri));
+ bitmap =
+ BitmapFactory.decodeStream(
+ mXmppConnectionService.getContentResolver().openInputStream(uri));
return getPepAvatar(bitmap, Bitmap.CompressFormat.PNG, 100);
} catch (Exception e) {
return null;
@@ -1073,18 +1176,24 @@ public class FileBackend {
private Avatar getPepAvatar(Bitmap bitmap, Bitmap.CompressFormat format, int quality) {
try {
ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream();
- Base64OutputStream mBase64OutputStream = new Base64OutputStream(mByteArrayOutputStream, Base64.DEFAULT);
+ Base64OutputStream mBase64OutputStream =
+ new Base64OutputStream(mByteArrayOutputStream, Base64.DEFAULT);
MessageDigest digest = MessageDigest.getInstance("SHA-1");
- DigestOutputStream mDigestOutputStream = new DigestOutputStream(mBase64OutputStream, digest);
+ DigestOutputStream mDigestOutputStream =
+ new DigestOutputStream(mBase64OutputStream, digest);
if (!bitmap.compress(format, quality, mDigestOutputStream)) {
return null;
}
mDigestOutputStream.flush();
mDigestOutputStream.close();
long chars = mByteArrayOutputStream.size();
- if (format != Bitmap.CompressFormat.PNG && quality >= 50 && chars >= Config.AVATAR_CHAR_LIMIT) {
+ if (format != Bitmap.CompressFormat.PNG
+ && quality >= 50
+ && chars >= Config.AVATAR_CHAR_LIMIT) {
int q = quality - 2;
- Log.d(Config.LOGTAG, "avatar char length was " + chars + " reducing quality to " + q);
+ Log.d(
+ Config.LOGTAG,
+ "avatar char length was " + chars + " reducing quality to " + q);
return getPepAvatar(bitmap, format, q);
}
Log.d(Config.LOGTAG, "settled on char length " + chars + " with quality=" + quality);
@@ -1123,7 +1232,8 @@ public class FileBackend {
BitmapFactory.decodeFile(file.getAbsolutePath(), options);
is = new FileInputStream(file);
ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream();
- Base64OutputStream mBase64OutputStream = new Base64OutputStream(mByteArrayOutputStream, Base64.DEFAULT);
+ Base64OutputStream mBase64OutputStream =
+ new Base64OutputStream(mByteArrayOutputStream, Base64.DEFAULT);
MessageDigest digest = MessageDigest.getInstance("SHA-1");
DigestOutputStream os = new DigestOutputStream(mBase64OutputStream, digest);
byte[] buffer = new byte[4096];
@@ -1157,14 +1267,20 @@ public class FileBackend {
file = getAvatarFile(avatar.getFilename());
avatar.size = file.length();
} else {
- file = new File(mXmppConnectionService.getCacheDir().getAbsolutePath() + "/" + UUID.randomUUID().toString());
+ file =
+ new File(
+ mXmppConnectionService.getCacheDir().getAbsolutePath()
+ + "/"
+ + UUID.randomUUID().toString());
if (file.getParentFile().mkdirs()) {
Log.d(Config.LOGTAG, "created cache directory");
}
OutputStream os = null;
try {
if (!file.createNewFile()) {
- Log.d(Config.LOGTAG, "unable to create temporary file " + file.getAbsolutePath());
+ Log.d(
+ Config.LOGTAG,
+ "unable to create temporary file " + file.getAbsolutePath());
}
os = new FileOutputStream(file);
MessageDigest digest = MessageDigest.getInstance("SHA-1");
@@ -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");
@@ -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);
@@ -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);
@@ -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();
}
@@ -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,
@@ -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]);
+ }
}
@@ -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;
-import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
+import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.ActivityRecordingBinding;
-import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.ui.util.SettingsUtils;
import eu.siacs.conversations.utils.ThemeHelper;
import eu.siacs.conversations.utils.TimeFrameUtils;
public class RecordingActivity extends Activity implements View.OnClickListener {
- public static String STORAGE_DIRECTORY_TYPE_NAME = "Recordings";
-
private ActivityRecordingBinding binding;
private MediaRecorder mRecorder;
@@ -44,13 +42,14 @@ public class RecordingActivity extends Activity implements View.OnClickListener
private final CountDownLatch outputFileWrittenLatch = new CountDownLatch(1);
private final Handler mHandler = new Handler();
- private final Runnable mTickExecutor = new Runnable() {
- @Override
- public void run() {
- tick();
- mHandler.postDelayed(mTickExecutor, 100);
- }
- };
+ private final Runnable mTickExecutor =
+ new Runnable() {
+ @Override
+ public void run() {
+ tick();
+ mHandler.postDelayed(mTickExecutor, 100);
+ }
+ };
private File mOutputFile;
@@ -68,7 +67,7 @@ public class RecordingActivity extends Activity implements View.OnClickListener
}
@Override
- protected void onResume(){
+ protected void onResume() {
super.onResume();
SettingsUtils.applyScreenshotPreventionSetting(this);
}
@@ -137,56 +136,69 @@ public class RecordingActivity extends Activity implements View.OnClickListener
}
}
if (saveFile) {
- new Thread(() -> {
- try {
- if (!outputFileWrittenLatch.await(2, TimeUnit.SECONDS)) {
- Log.d(Config.LOGTAG, "time out waiting for output file to be written");
- }
- } catch (InterruptedException e) {
- Log.d(Config.LOGTAG, "interrupted while waiting for output file to be written", e);
- }
- runOnUiThread(() -> {
- setResult(Activity.RESULT_OK, new Intent().setData(Uri.fromFile(mOutputFile)));
- finish();
- });
- }).start();
+ new Thread(
+ () -> {
+ try {
+ if (!outputFileWrittenLatch.await(2, TimeUnit.SECONDS)) {
+ Log.d(
+ Config.LOGTAG,
+ "time out waiting for output file to be written");
+ }
+ } catch (InterruptedException e) {
+ Log.d(
+ Config.LOGTAG,
+ "interrupted while waiting for output file to be written",
+ e);
+ }
+ runOnUiThread(
+ () -> {
+ setResult(
+ Activity.RESULT_OK,
+ new Intent()
+ .setData(Uri.fromFile(mOutputFile)));
+ finish();
+ });
+ })
+ .start();
}
}
- private static File generateOutputFilename(Context context) {
- SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
- String filename = "RECORDING_" + dateFormat.format(new Date()) + ".m4a";
- return new File(FileBackend.getConversationsDirectory(context, STORAGE_DIRECTORY_TYPE_NAME) + "/" + filename);
+ private File generateOutputFilename() {
+ final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
+ final String filename = "RECORDING_" + dateFormat.format(new Date()) + ".m4a";
+ final File parentDirectory;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ parentDirectory =
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_RECORDINGS);
+ } else {
+ parentDirectory =
+ Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
+ }
+ final File conversationsDirectory = new File(parentDirectory, getString(R.string.app_name));
+ return new File(conversationsDirectory, filename);
}
private void setupOutputFile() {
- mOutputFile = generateOutputFilename(this);
- File parentDirectory = mOutputFile.getParentFile();
- if (parentDirectory.mkdirs()) {
+ mOutputFile = generateOutputFilename();
+ final File parentDirectory = mOutputFile.getParentFile();
+ if (Objects.requireNonNull(parentDirectory).mkdirs()) {
Log.d(Config.LOGTAG, "created " + parentDirectory.getAbsolutePath());
}
- File noMedia = new File(parentDirectory, ".nomedia");
- if (!noMedia.exists()) {
- try {
- if (noMedia.createNewFile()) {
- Log.d(Config.LOGTAG, "created nomedia file in " + parentDirectory.getAbsolutePath());
- }
- } catch (IOException e) {
- Log.d(Config.LOGTAG, "unable to create nomedia file in " + parentDirectory.getAbsolutePath(), e);
- }
- }
setupFileObserver(parentDirectory);
}
private void setupFileObserver(File directory) {
- mFileObserver = new FileObserver(directory.getAbsolutePath()) {
- @Override
- public void onEvent(int event, String s) {
- if (s != null && s.equals(mOutputFile.getName()) && event == FileObserver.CLOSE_WRITE) {
- outputFileWrittenLatch.countDown();
- }
- }
- };
+ mFileObserver =
+ new FileObserver(directory.getAbsolutePath()) {
+ @Override
+ public void onEvent(int event, String s) {
+ if (s != null
+ && s.equals(mOutputFile.getName())
+ && event == FileObserver.CLOSE_WRITE) {
+ outputFileWrittenLatch.countDown();
+ }
+ }
+ };
mFileObserver.startWatching();
}
@@ -71,10 +71,9 @@ import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
import eu.siacs.conversations.xmpp.jingle.Media;
import eu.siacs.conversations.xmpp.jingle.RtpEndUserState;
-import static eu.siacs.conversations.utils.PermissionUtils.getFirstDenied;
-import static java.util.Arrays.asList;
-
-public class RtpSessionActivity extends XmppActivity implements XmppConnectionService.OnJingleRtpConnectionUpdate, eu.siacs.conversations.ui.widget.SurfaceViewRenderer.OnAspectRatioChanged {
+public class RtpSessionActivity extends XmppActivity
+ implements XmppConnectionService.OnJingleRtpConnectionUpdate,
+ eu.siacs.conversations.ui.widget.SurfaceViewRenderer.OnAspectRatioChanged {
public static final String EXTRA_WITH = "with";
public static final String EXTRA_SESSION_ID = "session_id";
@@ -86,33 +85,31 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
private static final int CALL_DURATION_UPDATE_INTERVAL = 333;
- public static final List<RtpEndUserState> END_CARD = Arrays.asList(
- RtpEndUserState.APPLICATION_ERROR,
- RtpEndUserState.SECURITY_ERROR,
- RtpEndUserState.DECLINED_OR_BUSY,
- RtpEndUserState.CONNECTIVITY_ERROR,
- RtpEndUserState.CONNECTIVITY_LOST_ERROR,
- RtpEndUserState.RETRACTED
- );
- private static final List<RtpEndUserState> STATES_SHOWING_HELP_BUTTON = Arrays.asList(
- RtpEndUserState.APPLICATION_ERROR,
- RtpEndUserState.CONNECTIVITY_ERROR,
- RtpEndUserState.SECURITY_ERROR
- );
- private static final List<RtpEndUserState> STATES_SHOWING_SWITCH_TO_CHAT = Arrays.asList(
- RtpEndUserState.CONNECTING,
- RtpEndUserState.CONNECTED,
- RtpEndUserState.RECONNECTING
- );
- private static final List<RtpEndUserState> STATES_CONSIDERED_CONNECTED = Arrays.asList(
- RtpEndUserState.CONNECTED,
- RtpEndUserState.RECONNECTING
- );
- private static final List<RtpEndUserState> STATES_SHOWING_PIP_PLACEHOLDER = Arrays.asList(
- RtpEndUserState.ACCEPTING_CALL,
- RtpEndUserState.CONNECTING,
- RtpEndUserState.RECONNECTING
- );
+ public static final List<RtpEndUserState> END_CARD =
+ Arrays.asList(
+ RtpEndUserState.APPLICATION_ERROR,
+ RtpEndUserState.SECURITY_ERROR,
+ RtpEndUserState.DECLINED_OR_BUSY,
+ RtpEndUserState.CONNECTIVITY_ERROR,
+ RtpEndUserState.CONNECTIVITY_LOST_ERROR,
+ RtpEndUserState.RETRACTED);
+ private static final List<RtpEndUserState> STATES_SHOWING_HELP_BUTTON =
+ Arrays.asList(
+ RtpEndUserState.APPLICATION_ERROR,
+ RtpEndUserState.CONNECTIVITY_ERROR,
+ RtpEndUserState.SECURITY_ERROR);
+ private static final List<RtpEndUserState> STATES_SHOWING_SWITCH_TO_CHAT =
+ Arrays.asList(
+ RtpEndUserState.CONNECTING,
+ RtpEndUserState.CONNECTED,
+ RtpEndUserState.RECONNECTING);
+ private static final List<RtpEndUserState> STATES_CONSIDERED_CONNECTED =
+ Arrays.asList(RtpEndUserState.CONNECTED, RtpEndUserState.RECONNECTING);
+ private static final List<RtpEndUserState> STATES_SHOWING_PIP_PLACEHOLDER =
+ Arrays.asList(
+ RtpEndUserState.ACCEPTING_CALL,
+ RtpEndUserState.CONNECTING,
+ RtpEndUserState.RECONNECTING);
private static final String PROXIMITY_WAKE_LOCK_TAG = "conversations:in-rtp-session";
private static final int REQUEST_ACCEPT_CALL = 0x1111;
private WeakReference<JingleRtpConnection> rtpConnectionReference;
@@ -121,13 +118,14 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
private PowerManager.WakeLock mProximityWakeLock;
private final Handler mHandler = new Handler();
- private final Runnable mTickExecutor = new Runnable() {
- @Override
- public void run() {
- updateCallDuration();
- mHandler.postDelayed(mTickExecutor, CALL_DURATION_UPDATE_INTERVAL);
- }
- };
+ private final Runnable mTickExecutor =
+ new Runnable() {
+ @Override
+ public void run() {
+ updateCallDuration();
+ mHandler.postDelayed(mTickExecutor, CALL_DURATION_UPDATE_INTERVAL);
+ }
+ };
private static Set<Media> actionToMedia(final String action) {
if (ACTION_MAKE_VIDEO_CALL.equals(action)) {
@@ -137,21 +135,27 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
}
}
- private static void addSink(final VideoTrack videoTrack, final SurfaceViewRenderer surfaceViewRenderer) {
+ private static void addSink(
+ final VideoTrack videoTrack, final SurfaceViewRenderer surfaceViewRenderer) {
try {
videoTrack.addSink(surfaceViewRenderer);
} catch (final IllegalStateException e) {
- Log.e(Config.LOGTAG, "possible race condition on trying to display video track. ignoring", e);
+ Log.e(
+ Config.LOGTAG,
+ "possible race condition on trying to display video track. ignoring",
+ e);
}
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
- | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
- | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
- | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
+ getWindow()
+ .addFlags(
+ WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
+ | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
+ | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+ | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_rtp_session);
setSupportActionBar(binding.toolbar);
@@ -194,7 +198,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
return STATES_SHOWING_HELP_BUTTON.contains(requireRtpConnection().getEndUserState());
} catch (IllegalStateException e) {
final Intent intent = getIntent();
- final String state = intent != null ? intent.getStringExtra(EXTRA_LAST_REPORTED_STATE) : null;
+ final String state =
+ intent != null ? intent.getStringExtra(EXTRA_LAST_REPORTED_STATE) : null;
if (state != null) {
return STATES_SHOWING_HELP_BUTTON.contains(RtpEndUserState.valueOf(state));
} else {
@@ -204,8 +209,10 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
}
private boolean isSwitchToConversationVisible() {
- final JingleRtpConnection connection = this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
- return connection != null && STATES_SHOWING_SWITCH_TO_CHAT.contains(connection.getEndUserState());
+ final JingleRtpConnection connection =
+ this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
+ return connection != null
+ && STATES_SHOWING_SWITCH_TO_CHAT.contains(connection.getEndUserState());
}
private boolean isAudioOnlyConversation() {
@@ -217,7 +224,9 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
private void switchToConversation() {
final Contact contact = getWith();
- final Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true);
+ final Conversation conversation =
+ xmppConnectionService.findOrCreateConversation(
+ contact.getAccount(), contact.getJid(), false, true);
switchToConversation(conversation);
}
@@ -250,7 +259,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
try {
startActivity(intent);
} catch (final ActivityNotFoundException e) {
- Toast.makeText(this, R.string.no_application_found_to_open_link, Toast.LENGTH_LONG).show();
+ Toast.makeText(this, R.string.no_application_found_to_open_link, Toast.LENGTH_LONG)
+ .show();
}
}
@@ -273,10 +283,15 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
final Account account = extractAccount(intent);
final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH));
final String state = intent.getStringExtra(EXTRA_LAST_REPORTED_STATE);
- if (!Intent.ACTION_VIEW.equals(action) || state == null || !END_CARD.contains(RtpEndUserState.valueOf(state))) {
- resetIntent(account, with, RtpEndUserState.RETRACTED, actionToMedia(intent.getAction()));
+ if (!Intent.ACTION_VIEW.equals(action)
+ || state == null
+ || !END_CARD.contains(RtpEndUserState.valueOf(state))) {
+ resetIntent(
+ account, with, RtpEndUserState.RETRACTED, actionToMedia(intent.getAction()));
}
- xmppConnectionService.getJingleConnectionManager().retractSessionProposal(account, with.asBareJid());
+ xmppConnectionService
+ .getJingleConnectionManager()
+ .retractSessionProposal(account, with.asBareJid());
}
private void rejectCall(View view) {
@@ -291,7 +306,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
private void requestPermissionsAndAcceptCall() {
final List<String> permissions;
if (getMedia().contains(Media.VIDEO)) {
- permissions = ImmutableList.of(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO);
+ permissions =
+ ImmutableList.of(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO);
} else {
permissions = ImmutableList.of(Manifest.permission.RECORD_AUDIO);
}
@@ -302,7 +318,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
}
private void checkRecorderAndAcceptCall() {
- checkMicrophoneAvailability();
+ checkMicrophoneAvailabilityAsync();
try {
requireRtpConnection().acceptCall();
} catch (final IllegalStateException e) {
@@ -310,18 +326,22 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
}
}
+ private void checkMicrophoneAvailabilityAsync() {
+ new Thread(this::checkMicrophoneAvailability).start();
+ }
+
private void checkMicrophoneAvailability() {
- new Thread(() -> {
- final long start = SystemClock.elapsedRealtime();
- final boolean isMicrophoneAvailable = AppRTCAudioManager.isMicrophoneAvailable();
- final long stop = SystemClock.elapsedRealtime();
- Log.d(Config.LOGTAG, "checking microphone availability took " + (stop - start) + "ms");
- if (isMicrophoneAvailable) {
- return;
- }
- runOnUiThread(() -> Toast.makeText(this, R.string.microphone_unavailable, Toast.LENGTH_LONG).show());
+ final long start = SystemClock.elapsedRealtime();
+ final boolean isMicrophoneAvailable = AppRTCAudioManager.isMicrophoneAvailable();
+ final long stop = SystemClock.elapsedRealtime();
+ Log.d(Config.LOGTAG, "checking microphone availability took " + (stop - start) + "ms");
+ if (isMicrophoneAvailable) {
+ return;
}
- ).start();
+ runOnUiThread(
+ () ->
+ Toast.makeText(this, R.string.microphone_unavailable, Toast.LENGTH_LONG)
+ .show());
}
private void putScreenInCallMode() {
@@ -331,9 +351,13 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
private void putScreenInCallMode(final Set<Media> media) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
if (!media.contains(Media.VIDEO)) {
- final JingleRtpConnection rtpConnection = rtpConnectionReference != null ? rtpConnectionReference.get() : null;
- final AppRTCAudioManager audioManager = rtpConnection == null ? null : rtpConnection.getAudioManager();
- if (audioManager == null || audioManager.getSelectedAudioDevice() == AppRTCAudioManager.AudioDevice.EARPIECE) {
+ final JingleRtpConnection rtpConnection =
+ rtpConnectionReference != null ? rtpConnectionReference.get() : null;
+ final AppRTCAudioManager audioManager =
+ rtpConnection == null ? null : rtpConnection.getAudioManager();
+ if (audioManager == null
+ || audioManager.getSelectedAudioDevice()
+ == AppRTCAudioManager.AudioDevice.EARPIECE) {
acquireProximityWakeLock();
}
}
@@ -346,30 +370,31 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
Log.e(Config.LOGTAG, "power manager not available");
return;
}
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- if (this.mProximityWakeLock == null) {
- this.mProximityWakeLock = powerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, PROXIMITY_WAKE_LOCK_TAG);
- }
- if (!this.mProximityWakeLock.isHeld()) {
- Log.d(Config.LOGTAG, "acquiring proximity wake lock");
- this.mProximityWakeLock.acquire();
- }
+ if (isFinishing()) {
+ Log.e(Config.LOGTAG, "do not acquire wakelock. activity is finishing");
+ return;
+ }
+ if (this.mProximityWakeLock == null) {
+ this.mProximityWakeLock =
+ powerManager.newWakeLock(
+ PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, PROXIMITY_WAKE_LOCK_TAG);
+ }
+ if (!this.mProximityWakeLock.isHeld()) {
+ Log.d(Config.LOGTAG, "acquiring proximity wake lock");
+ this.mProximityWakeLock.acquire();
}
}
private void releaseProximityWakeLock() {
if (this.mProximityWakeLock != null && mProximityWakeLock.isHeld()) {
Log.d(Config.LOGTAG, "releasing proximity wake lock");
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- this.mProximityWakeLock.release(PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY);
- } else {
- this.mProximityWakeLock.release();
- }
+ this.mProximityWakeLock.release(PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY);
this.mProximityWakeLock = null;
}
}
- private void putProximityWakeLockInProperState(final AppRTCAudioManager.AudioDevice audioDevice) {
+ private void putProximityWakeLockInProperState(
+ final AppRTCAudioManager.AudioDevice audioDevice) {
if (audioDevice == AppRTCAudioManager.AudioDevice.EARPIECE) {
acquireProximityWakeLock();
} else {
@@ -378,9 +403,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
}
@Override
- protected void refreshUiReal() {
-
- }
+ protected void refreshUiReal() {}
@Override
public void onNewIntent(final Intent intent) {
@@ -388,7 +411,9 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
super.onNewIntent(intent);
setIntent(intent);
if (xmppConnectionService == null) {
- Log.d(Config.LOGTAG, "RtpSessionActivity: background service wasn't bound in onNewIntent()");
+ Log.d(
+ Config.LOGTAG,
+ "RtpSessionActivity: background service wasn't bound in onNewIntent()");
return;
}
final Account account = extractAccount(intent);
@@ -407,8 +432,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
}
} else if (asList(ACTION_MAKE_VIDEO_CALL, ACTION_MAKE_VOICE_CALL).contains(action)) {
proposeJingleRtpSession(account, with, actionToMedia(action));
- binding.with.setText(account.getRoster().getContact(with).getDisplayName());
- binding.withJid.setText(with.asBareJid());
+ setWith(account.getRoster().getContact(with));
} else {
throw new IllegalStateException("received onNewIntent without sessionId");
}
@@ -432,25 +456,29 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
}
} else if (asList(ACTION_MAKE_VIDEO_CALL, ACTION_MAKE_VOICE_CALL).contains(action)) {
proposeJingleRtpSession(account, with, actionToMedia(action));
- binding.with.setText(account.getRoster().getContact(with).getDisplayName());
- binding.withJid.setText(with.asBareJid());
+ setWith(account.getRoster().getContact(with));
} else if (Intent.ACTION_VIEW.equals(action)) {
final String extraLastState = intent.getStringExtra(EXTRA_LAST_REPORTED_STATE);
- final RtpEndUserState state = extraLastState == null ? null : RtpEndUserState.valueOf(extraLastState);
+ final RtpEndUserState state =
+ extraLastState == null ? null : RtpEndUserState.valueOf(extraLastState);
if (state != null) {
Log.d(Config.LOGTAG, "restored last state from intent extra");
updateButtonConfiguration(state);
updateVerifiedShield(false);
updateStateDisplay(state);
- updateProfilePicture(state);
+ updateIncomingCallScreen(state);
invalidateOptionsMenu();
}
- binding.with.setText(account.getRoster().getContact(with).getDisplayName());
- binding.withJid.setText(with.asBareJid());
- if (xmppConnectionService.getJingleConnectionManager().fireJingleRtpConnectionStateUpdates()) {
+ setWith(account.getRoster().getContact(with));
+ if (xmppConnectionService
+ .getJingleConnectionManager()
+ .fireJingleRtpConnectionStateUpdates()) {
return;
}
- if (END_CARD.contains(state) || xmppConnectionService.getJingleConnectionManager().hasMatchingProposal(account, with)) {
+ if (END_CARD.contains(state)
+ || xmppConnectionService
+ .getJingleConnectionManager()
+ .hasMatchingProposal(account, with)) {
return;
}
Log.d(Config.LOGTAG, "restored state (" + state + ") was not an end card. finishing");
@@ -458,12 +486,27 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
}
}
- private void proposeJingleRtpSession(final Account account, final Jid with, final Set<Media> media) {
- checkMicrophoneAvailability();
+ private void setWith() {
+ setWith(getWith());
+ }
+
+ private void setWith(final Contact contact) {
+ binding.with.setText(contact.getDisplayName());
+ binding.withJid.setText(contact.getJid().asBareJid().toEscapedString());
+ }
+
+ private void proposeJingleRtpSession(
+ final Account account, final Jid with, final Set<Media> media) {
+ checkMicrophoneAvailabilityAsync();
if (with.isBareJid()) {
- xmppConnectionService.getJingleConnectionManager().proposeJingleRtpSession(account, with, media);
+ xmppConnectionService
+ .getJingleConnectionManager()
+ .proposeJingleRtpSession(account, with, media);
} else {
- final String sessionId = xmppConnectionService.getJingleConnectionManager().initializeRtpSession(account, with, media);
+ final String sessionId =
+ xmppConnectionService
+ .getJingleConnectionManager()
+ .initializeRtpSession(account, with, media);
initializeActivityWithRunningRtpSession(account, with, sessionId);
resetIntent(account, with, sessionId);
}
@@ -471,7 +514,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
}
@Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ public void onRequestPermissionsResult(
+ int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (PermissionUtils.allGranted(grantResults)) {
if (requestCode == REQUEST_ACCEPT_CALL) {
@@ -487,7 +531,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
} else {
throw new IllegalStateException("Invalid permission result request");
}
- Toast.makeText(this, getString(res, getString(R.string.app_name)), Toast.LENGTH_SHORT).show();
+ Toast.makeText(this, getString(res, getString(R.string.app_name)), Toast.LENGTH_SHORT)
+ .show();
}
}
@@ -505,7 +550,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
binding.remoteVideo.setOnAspectRatioChanged(null);
binding.localVideo.release();
final WeakReference<JingleRtpConnection> weakReference = this.rtpConnectionReference;
- final JingleRtpConnection jingleRtpConnection = weakReference == null ? null : weakReference.get();
+ final JingleRtpConnection jingleRtpConnection =
+ weakReference == null ? null : weakReference.get();
if (jingleRtpConnection != null) {
releaseVideoTracks(jingleRtpConnection);
}
@@ -542,15 +588,18 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
if (switchToPictureInPicture()) {
return;
}
- //TODO apparently this method is not getting called on Android 10 when using the task switcher
+ // TODO apparently this method is not getting called on Android 10 when using the task
+ // switcher
if (emptyReference(rtpConnectionReference) && xmppConnectionService != null) {
retractSessionProposal();
}
}
private boolean isConnected() {
- final JingleRtpConnection connection = this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
- return connection != null && STATES_CONSIDERED_CONNECTED.contains(connection.getEndUserState());
+ final JingleRtpConnection connection =
+ this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
+ return connection != null
+ && STATES_CONSIDERED_CONNECTED.contains(connection.getEndUserState());
}
private boolean switchToPictureInPicture() {
@@ -568,14 +617,13 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
try {
final Rational rational = this.binding.remoteVideo.getAspectRatio();
final Rational clippedRational = Rationals.clip(rational);
- Log.d(Config.LOGTAG, "suggested rational " + rational + ". clipped to " + clippedRational);
+ Log.d(
+ Config.LOGTAG,
+ "suggested rational " + rational + ". clipped to " + clippedRational);
enterPictureInPictureMode(
- new PictureInPictureParams.Builder()
- .setAspectRatio(clippedRational)
- .build()
- );
+ new PictureInPictureParams.Builder().setAspectRatio(clippedRational).build());
} catch (final IllegalStateException e) {
- //this sometimes happens on Samsung phones (possibly when Knox is enabled)
+ // this sometimes happens on Samsung phones (possibly when Knox is enabled)
Log.w(Config.LOGTAG, "unable to enter picture in picture mode", e);
}
}
@@ -584,10 +632,14 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
public void onAspectRatioChanged(final Rational rational) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isPictureInPicture()) {
final Rational clippedRational = Rationals.clip(rational);
- Log.d(Config.LOGTAG, "suggested rational after aspect ratio change " + rational + ". clipped to " + clippedRational);
- setPictureInPictureParams(new PictureInPictureParams.Builder()
- .setAspectRatio(clippedRational)
- .build());
+ Log.d(
+ Config.LOGTAG,
+ "suggested rational after aspect ratio change "
+ + rational
+ + ". clipped to "
+ + clippedRational);
+ setPictureInPictureParams(
+ new PictureInPictureParams.Builder().setAspectRatio(clippedRational).build());
}
}
@@ -602,24 +654,31 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
private boolean shouldBePictureInPicture() {
try {
final JingleRtpConnection rtpConnection = requireRtpConnection();
- return rtpConnection.getMedia().contains(Media.VIDEO) && Arrays.asList(
- RtpEndUserState.ACCEPTING_CALL,
- RtpEndUserState.CONNECTING,
- RtpEndUserState.CONNECTED
- ).contains(rtpConnection.getEndUserState());
+ return rtpConnection.getMedia().contains(Media.VIDEO)
+ && Arrays.asList(
+ RtpEndUserState.ACCEPTING_CALL,
+ RtpEndUserState.CONNECTING,
+ RtpEndUserState.CONNECTED)
+ .contains(rtpConnection.getEndUserState());
} catch (final IllegalStateException e) {
return false;
}
}
- private boolean initializeActivityWithRunningRtpSession(final Account account, Jid with, String sessionId) {
- final WeakReference<JingleRtpConnection> reference = xmppConnectionService.getJingleConnectionManager()
- .findJingleRtpConnection(account, with, sessionId);
+ private boolean initializeActivityWithRunningRtpSession(
+ final Account account, Jid with, String sessionId) {
+ final WeakReference<JingleRtpConnection> reference =
+ xmppConnectionService
+ .getJingleConnectionManager()
+ .findJingleRtpConnection(account, with, sessionId);
if (reference == null || reference.get() == null) {
- final JingleConnectionManager.TerminatedRtpSession terminatedRtpSession = xmppConnectionService
- .getJingleConnectionManager().getTerminalSessionState(with, sessionId);
+ final JingleConnectionManager.TerminatedRtpSession terminatedRtpSession =
+ xmppConnectionService
+ .getJingleConnectionManager()
+ .getTerminalSessionState(with, sessionId);
if (terminatedRtpSession == null) {
- throw new IllegalStateException("failed to initialize activity with running rtp session. session not found");
+ throw new IllegalStateException(
+ "failed to initialize activity with running rtp session. session not found");
}
initializeWithTerminatedSessionState(account, with, terminatedRtpSession);
return true;
@@ -628,7 +687,6 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
final RtpEndUserState currentState = requireRtpConnection().getEndUserState();
final boolean verified = requireRtpConnection().isVerified();
if (currentState == RtpEndUserState.ENDED) {
- reference.get().throwStateTransitionException();
finish();
return true;
}
@@ -636,21 +694,24 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
if (currentState == RtpEndUserState.INCOMING_CALL) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
- if (JingleRtpConnection.STATES_SHOWING_ONGOING_CALL.contains(requireRtpConnection().getState())) {
+ if (JingleRtpConnection.STATES_SHOWING_ONGOING_CALL.contains(
+ requireRtpConnection().getState())) {
putScreenInCallMode();
}
- binding.with.setText(getWith().getDisplayName());
- binding.withJid.setText(with.asBareJid());
+ setWith();
updateVideoViews(currentState);
updateStateDisplay(currentState, media);
updateVerifiedShield(verified && STATES_SHOWING_SWITCH_TO_CHAT.contains(currentState));
updateButtonConfiguration(currentState, media);
- updateProfilePicture(currentState);
+ updateIncomingCallScreen(currentState);
invalidateOptionsMenu();
return false;
}
- private void initializeWithTerminatedSessionState(final Account account, final Jid with, final JingleConnectionManager.TerminatedRtpSession terminatedRtpSession) {
+ private void initializeWithTerminatedSessionState(
+ final Account account,
+ final Jid with,
+ final JingleConnectionManager.TerminatedRtpSession terminatedRtpSession) {
Log.d(Config.LOGTAG, "initializeWithTerminatedSessionState()");
if (terminatedRtpSession.state == RtpEndUserState.ENDED) {
finish();
@@ -660,15 +721,15 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
resetIntent(account, with, terminatedRtpSession.state, terminatedRtpSession.media);
updateButtonConfiguration(state);
updateStateDisplay(state);
- updateProfilePicture(state);
+ updateIncomingCallScreen(state);
updateCallDuration();
updateVerifiedShield(false);
invalidateOptionsMenu();
- binding.with.setText(account.getRoster().getContact(with).getDisplayName());
- binding.withJid.setText(with.asBareJid());
+ setWith(account.getRoster().getContact(with));
}
- private void reInitializeActivityWithRunningRtpSession(final Account account, Jid with, String sessionId) {
+ private void reInitializeActivityWithRunningRtpSession(
+ final Account account, Jid with, String sessionId) {
runOnUiThread(() -> initializeActivityWithRunningRtpSession(account, with, sessionId));
resetIntent(account, with, sessionId);
}
@@ -686,7 +747,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
try {
surfaceViewRenderer.init(requireRtpConnection().getEglBaseContext(), null);
} catch (final IllegalStateException e) {
- //Log.d(Config.LOGTAG, "SurfaceViewRenderer was already initialized");
+ // Log.d(Config.LOGTAG, "SurfaceViewRenderer was already initialized");
}
surfaceViewRenderer.setEnableHardwareScaler(true);
}
@@ -745,9 +806,11 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
setTitle(R.string.rtp_state_security_error);
break;
case ENDED:
- throw new IllegalStateException("Activity should have called finishAndReleaseWakeLock();");
+ throw new IllegalStateException(
+ "Activity should have called finishAndReleaseWakeLock();");
default:
- throw new IllegalStateException(String.format("State %s has not been handled in UI", state));
+ throw new IllegalStateException(
+ String.format("State %s has not been handled in UI", state));
}
}
@@ -759,24 +822,33 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
this.binding.verified.setVisibility(verified ? View.VISIBLE : View.GONE);
}
- private void updateProfilePicture(final RtpEndUserState state) {
- updateProfilePicture(state, null);
+ private void updateIncomingCallScreen(final RtpEndUserState state) {
+ updateIncomingCallScreen(state, null);
}
- private void updateProfilePicture(final RtpEndUserState state, final Contact contact) {
+ private void updateIncomingCallScreen(final RtpEndUserState state, final Contact contact) {
if (state == RtpEndUserState.INCOMING_CALL || state == RtpEndUserState.ACCEPTING_CALL) {
final boolean show = getResources().getBoolean(R.bool.show_avatar_incoming_call);
if (show) {
binding.contactPhoto.setVisibility(View.VISIBLE);
if (contact == null) {
- AvatarWorkerTask.loadAvatar(getWith(), binding.contactPhoto, R.dimen.publish_avatar_size);
+ AvatarWorkerTask.loadAvatar(
+ getWith(), binding.contactPhoto, R.dimen.publish_avatar_size);
} else {
- AvatarWorkerTask.loadAvatar(contact, binding.contactPhoto, R.dimen.publish_avatar_size);
+ AvatarWorkerTask.loadAvatar(
+ contact, binding.contactPhoto, R.dimen.publish_avatar_size);
}
} else {
binding.contactPhoto.setVisibility(View.GONE);
}
+ final Account account = contact == null ? getWith().getAccount() : contact.getAccount();
+ binding.usingAccount.setVisibility(View.VISIBLE);
+ binding.usingAccount.setText(
+ getString(
+ R.string.using_account,
+ account.getJid().asBareJid().toEscapedString()));
} else {
+ binding.usingAccount.setVisibility(View.GONE);
binding.contactPhoto.setVisibility(View.GONE);
}
}
@@ -816,12 +888,12 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
this.binding.acceptCall.setImageResource(R.drawable.ic_voicemail_white_24dp);
this.binding.acceptCall.setVisibility(View.VISIBLE);
} else if (asList(
- RtpEndUserState.CONNECTIVITY_ERROR,
- RtpEndUserState.CONNECTIVITY_LOST_ERROR,
- RtpEndUserState.APPLICATION_ERROR,
- RtpEndUserState.RETRACTED,
- RtpEndUserState.SECURITY_ERROR
- ).contains(state)) {
+ RtpEndUserState.CONNECTIVITY_ERROR,
+ RtpEndUserState.CONNECTIVITY_LOST_ERROR,
+ RtpEndUserState.APPLICATION_ERROR,
+ RtpEndUserState.RETRACTED,
+ RtpEndUserState.SECURITY_ERROR)
+ .contains(state)) {
this.binding.rejectCall.setContentDescription(getString(R.string.exit));
this.binding.rejectCall.setOnClickListener(this::exit);
this.binding.rejectCall.setImageResource(R.drawable.ic_clear_white_48dp);
@@ -851,26 +923,29 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
}
private void updateInCallButtonConfiguration() {
- updateInCallButtonConfiguration(requireRtpConnection().getEndUserState(), requireRtpConnection().getMedia());
+ updateInCallButtonConfiguration(
+ requireRtpConnection().getEndUserState(), requireRtpConnection().getMedia());
}
@SuppressLint("RestrictedApi")
- private void updateInCallButtonConfiguration(final RtpEndUserState state, final Set<Media> media) {
+ private void updateInCallButtonConfiguration(
+ final RtpEndUserState state, final Set<Media> media) {
if (STATES_CONSIDERED_CONNECTED.contains(state) && !isPictureInPicture()) {
Preconditions.checkArgument(media.size() > 0, "Media must not be empty");
if (media.contains(Media.VIDEO)) {
final JingleRtpConnection rtpConnection = requireRtpConnection();
- updateInCallButtonConfigurationVideo(rtpConnection.isVideoEnabled(), rtpConnection.isCameraSwitchable());
+ updateInCallButtonConfigurationVideo(
+ rtpConnection.isVideoEnabled(), rtpConnection.isCameraSwitchable());
} else {
final AppRTCAudioManager audioManager = requireRtpConnection().getAudioManager();
updateInCallButtonConfigurationSpeaker(
audioManager.getSelectedAudioDevice(),
- audioManager.getAudioDevices().size()
- );
+ audioManager.getAudioDevices().size());
this.binding.inCallActionFarRight.setVisibility(View.GONE);
}
if (media.contains(Media.AUDIO)) {
- updateInCallButtonConfigurationMicrophone(requireRtpConnection().isMicrophoneEnabled());
+ updateInCallButtonConfigurationMicrophone(
+ requireRtpConnection().isMicrophoneEnabled());
} else {
this.binding.inCallActionLeft.setVisibility(View.GONE);
}
@@ -882,10 +957,12 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
}
@SuppressLint("RestrictedApi")
- private void updateInCallButtonConfigurationSpeaker(final AppRTCAudioManager.AudioDevice selectedAudioDevice, final int numberOfChoices) {
+ private void updateInCallButtonConfigurationSpeaker(
+ final AppRTCAudioManager.AudioDevice selectedAudioDevice, final int numberOfChoices) {
switch (selectedAudioDevice) {
case EARPIECE:
- this.binding.inCallActionRight.setImageResource(R.drawable.ic_volume_off_black_24dp);
+ this.binding.inCallActionRight.setImageResource(
+ R.drawable.ic_volume_off_black_24dp);
if (numberOfChoices >= 2) {
this.binding.inCallActionRight.setOnClickListener(this::switchToSpeaker);
} else {
@@ -908,7 +985,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
}
break;
case BLUETOOTH:
- this.binding.inCallActionRight.setImageResource(R.drawable.ic_bluetooth_audio_black_24dp);
+ this.binding.inCallActionRight.setImageResource(
+ R.drawable.ic_bluetooth_audio_black_24dp);
this.binding.inCallActionRight.setOnClickListener(null);
this.binding.inCallActionRight.setClickable(false);
break;
@@ -917,10 +995,12 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
}
@SuppressLint("RestrictedApi")
- private void updateInCallButtonConfigurationVideo(final boolean videoEnabled, final boolean isCameraSwitchable) {
+ private void updateInCallButtonConfigurationVideo(
+ final boolean videoEnabled, final boolean isCameraSwitchable) {
this.binding.inCallActionRight.setVisibility(View.VISIBLE);
if (isCameraSwitchable) {
- this.binding.inCallActionFarRight.setImageResource(R.drawable.ic_flip_camera_android_black_24dp);
+ this.binding.inCallActionFarRight.setImageResource(
+ R.drawable.ic_flip_camera_android_black_24dp);
this.binding.inCallActionFarRight.setVisibility(View.VISIBLE);
this.binding.inCallActionFarRight.setOnClickListener(this::switchCamera);
} else {
@@ -936,18 +1016,28 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
}
private void switchCamera(final View view) {
- Futures.addCallback(requireRtpConnection().switchCamera(), new FutureCallback<Boolean>() {
- @Override
- public void onSuccess(@NullableDecl Boolean isFrontCamera) {
- binding.localVideo.setMirror(isFrontCamera);
- }
-
- @Override
- public void onFailure(@NonNull final Throwable throwable) {
- Log.d(Config.LOGTAG, "could not switch camera", Throwables.getRootCause(throwable));
- Toast.makeText(RtpSessionActivity.this, R.string.could_not_switch_camera, Toast.LENGTH_LONG).show();
- }
- }, MainThreadExecutor.getInstance());
+ Futures.addCallback(
+ requireRtpConnection().switchCamera(),
+ new FutureCallback<Boolean>() {
+ @Override
+ public void onSuccess(@NullableDecl Boolean isFrontCamera) {
+ binding.localVideo.setMirror(isFrontCamera);
+ }
+
+ @Override
+ public void onFailure(@NonNull final Throwable throwable) {
+ Log.d(
+ Config.LOGTAG,
+ "could not switch camera",
+ Throwables.getRootCause(throwable));
+ Toast.makeText(
+ RtpSessionActivity.this,
+ R.string.could_not_switch_camera,
+ Toast.LENGTH_LONG)
+ .show();
+ }
+ },
+ MainThreadExecutor.getInstance());
}
private void enableVideo(View view) {
@@ -963,7 +1053,6 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
private void disableVideo(View view) {
requireRtpConnection().setVideoEnabled(false);
updateInCallButtonConfigurationVideo(false, requireRtpConnection().isCameraSwitchable());
-
}
@SuppressLint("RestrictedApi")
@@ -979,7 +1068,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
}
private void updateCallDuration() {
- final JingleRtpConnection connection = this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
+ final JingleRtpConnection connection =
+ this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
if (connection == null || connection.getMedia().contains(Media.VIDEO)) {
this.binding.duration.setVisibility(View.GONE);
return;
@@ -987,7 +1077,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
if (connection.zeroDuration()) {
this.binding.duration.setVisibility(View.GONE);
} else {
- this.binding.duration.setText(TimeFrameUtils.formatElapsedTime(connection.getCallDuration(), false));
+ this.binding.duration.setText(
+ TimeFrameUtils.formatElapsedTime(connection.getCallDuration(), false));
this.binding.duration.setVisibility(View.VISIBLE);
}
}
@@ -1003,9 +1094,9 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
binding.appBarLayout.setVisibility(View.GONE);
binding.pipPlaceholder.setVisibility(View.VISIBLE);
if (Arrays.asList(
- RtpEndUserState.APPLICATION_ERROR,
- RtpEndUserState.CONNECTIVITY_ERROR,
- RtpEndUserState.SECURITY_ERROR)
+ RtpEndUserState.APPLICATION_ERROR,
+ RtpEndUserState.CONNECTIVITY_ERROR,
+ RtpEndUserState.SECURITY_ERROR)
.contains(state)) {
binding.pipWarning.setVisibility(View.VISIBLE);
binding.pipWaiting.setVisibility(View.GONE);
@@ -1033,7 +1124,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
final Optional<VideoTrack> localVideoTrack = getLocalVideoTrack();
if (localVideoTrack.isPresent() && !isPictureInPicture()) {
ensureSurfaceViewRendererIsSetup(binding.localVideo);
- //paint local view over remote view
+ // paint local view over remote view
binding.localVideo.setZOrderMediaOverlay(true);
binding.localVideo.setMirror(requireRtpConnection().isFrontCamera());
addSink(localVideoTrack.get(), binding.localVideo);
@@ -1046,8 +1137,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
addSink(remoteVideoTrack.get(), binding.remoteVideo);
binding.remoteVideo.setScalingType(
RendererCommon.ScalingType.SCALE_ASPECT_FILL,
- RendererCommon.ScalingType.SCALE_ASPECT_FIT
- );
+ RendererCommon.ScalingType.SCALE_ASPECT_FIT);
if (state == RtpEndUserState.CONNECTED) {
binding.appBarLayout.setVisibility(View.GONE);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
@@ -1070,7 +1160,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
}
private Optional<VideoTrack> getLocalVideoTrack() {
- final JingleRtpConnection connection = this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
+ final JingleRtpConnection connection =
+ this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
if (connection == null) {
return Optional.absent();
}
@@ -1078,7 +1169,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
}
private Optional<VideoTrack> getRemoteVideoTrack() {
- final JingleRtpConnection connection = this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
+ final JingleRtpConnection connection =
+ this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
if (connection == null) {
return Optional.absent();
}
@@ -1100,12 +1192,16 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
}
private void switchToEarpiece(View view) {
- requireRtpConnection().getAudioManager().setDefaultAudioDevice(AppRTCAudioManager.AudioDevice.EARPIECE);
+ requireRtpConnection()
+ .getAudioManager()
+ .setDefaultAudioDevice(AppRTCAudioManager.AudioDevice.EARPIECE);
acquireProximityWakeLock();
}
private void switchToSpeaker(View view) {
- requireRtpConnection().getAudioManager().setDefaultAudioDevice(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE);
+ requireRtpConnection()
+ .getAudioManager()
+ .setDefaultAudioDevice(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE);
releaseProximityWakeLock();
}
@@ -1129,12 +1225,15 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
final Intent intent = getIntent();
final Account account = extractAccount(intent);
final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH));
- final Conversation conversation = xmppConnectionService.findOrCreateConversation(account, with, false, true);
+ final Conversation conversation =
+ xmppConnectionService.findOrCreateConversation(account, with, false, true);
final Intent launchIntent = new Intent(this, ConversationsActivity.class);
launchIntent.setAction(ConversationsActivity.ACTION_VIEW_CONVERSATION);
launchIntent.putExtra(ConversationsActivity.EXTRA_CONVERSATION, conversation.getUuid());
launchIntent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_CLEAR_TOP);
- launchIntent.putExtra(ConversationsActivity.EXTRA_POST_INIT_ACTION, ConversationsActivity.POST_ACTION_RECORD_VOICE);
+ launchIntent.putExtra(
+ ConversationsActivity.EXTRA_POST_INIT_ACTION,
+ ConversationsActivity.POST_ACTION_RECORD_VOICE);
startActivity(launchIntent);
finish();
}
@@ -1146,7 +1245,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
}
private JingleRtpConnection requireRtpConnection() {
- final JingleRtpConnection connection = this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
+ final JingleRtpConnection connection =
+ this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
if (connection == null) {
throw new IllegalStateException("No RTP connection found");
}
@@ -1154,12 +1254,14 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
}
@Override
- public void onJingleRtpConnectionUpdate(Account account, Jid with, final String sessionId, RtpEndUserState state) {
+ public void onJingleRtpConnectionUpdate(
+ Account account, Jid with, final String sessionId, RtpEndUserState state) {
Log.d(Config.LOGTAG, "onJingleRtpConnectionUpdate(" + state + ")");
if (END_CARD.contains(state)) {
Log.d(Config.LOGTAG, "end card reached");
releaseProximityWakeLock();
- runOnUiThread(() -> getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON));
+ runOnUiThread(
+ () -> getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON));
}
if (with.isBareJid()) {
updateRtpSessionProposalState(account, with, state);
@@ -1170,7 +1272,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
Log.d(Config.LOGTAG, "not reinitializing session");
return;
}
- //this happens when going from proposed session to actual session
+ // this happens when going from proposed session to actual session
reInitializeActivityWithRunningRtpSession(account, with, sessionId);
return;
}
@@ -1183,14 +1285,16 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
finish();
return;
}
- runOnUiThread(() -> {
- updateStateDisplay(state, media);
- updateVerifiedShield(verified && STATES_SHOWING_SWITCH_TO_CHAT.contains(state));
- updateButtonConfiguration(state, media);
- updateVideoViews(state);
- updateProfilePicture(state, contact);
- invalidateOptionsMenu();
- });
+ runOnUiThread(
+ () -> {
+ updateStateDisplay(state, media);
+ updateVerifiedShield(
+ verified && STATES_SHOWING_SWITCH_TO_CHAT.contains(state));
+ updateButtonConfiguration(state, media);
+ updateVideoViews(state);
+ updateIncomingCallScreen(state, contact);
+ invalidateOptionsMenu();
+ });
if (END_CARD.contains(state)) {
final JingleRtpConnection rtpConnection = requireRtpConnection();
resetIntent(account, with, state, rtpConnection.getMedia());
@@ -1203,8 +1307,15 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
}
@Override
- public void onAudioDeviceChanged(AppRTCAudioManager.AudioDevice selectedAudioDevice, Set<AppRTCAudioManager.AudioDevice> availableAudioDevices) {
- Log.d(Config.LOGTAG, "onAudioDeviceChanged in activity: selected:" + selectedAudioDevice + ", available:" + availableAudioDevices);
+ public void onAudioDeviceChanged(
+ AppRTCAudioManager.AudioDevice selectedAudioDevice,
+ Set<AppRTCAudioManager.AudioDevice> availableAudioDevices) {
+ Log.d(
+ Config.LOGTAG,
+ "onAudioDeviceChanged in activity: selected:"
+ + selectedAudioDevice
+ + ", available:"
+ + availableAudioDevices);
try {
if (getMedia().contains(Media.VIDEO)) {
Log.d(Config.LOGTAG, "nothing to do; in video mode");
@@ -1215,10 +1326,11 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
final AppRTCAudioManager audioManager = requireRtpConnection().getAudioManager();
updateInCallButtonConfigurationSpeaker(
audioManager.getSelectedAudioDevice(),
- audioManager.getAudioDevices().size()
- );
+ audioManager.getAudioDevices().size());
} else if (END_CARD.contains(endUserState)) {
- Log.d(Config.LOGTAG, "onAudioDeviceChanged() nothing to do because end card has been reached");
+ Log.d(
+ Config.LOGTAG,
+ "onAudioDeviceChanged() nothing to do because end card has been reached");
} else {
putProximityWakeLockInProperState(selectedAudioDevice);
}
@@ -224,7 +224,7 @@ public class SettingsActivity extends XmppActivity implements
final Preference createBackupPreference = mSettingsFragment.findPreference("create_backup");
if (createBackupPreference != null) {
- createBackupPreference.setSummary(getString(R.string.pref_create_backup_summary, FileBackend.getBackupDirectory(this)));
+ createBackupPreference.setSummary(getString(R.string.pref_create_backup_summary, FileBackend.getBackupDirectory(this).getAbsolutePath()));
createBackupPreference.setOnPreferenceClickListener(preference -> {
if (hasStoragePermission(REQUEST_CREATE_BACKUP)) {
createBackup();
@@ -71,10 +71,10 @@ import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.services.BarcodeProvider;
+import eu.siacs.conversations.services.EmojiInitializationService;
import eu.siacs.conversations.services.QuickConversationsService;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
-import eu.siacs.conversations.ui.service.EmojiService;
import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
import eu.siacs.conversations.ui.util.PresenceSelector;
import eu.siacs.conversations.ui.util.SoftKeyboardUtils;
@@ -408,7 +408,7 @@ public abstract class XmppActivity extends ActionBarActivity {
super.onCreate(savedInstanceState);
metrics = getResources().getDisplayMetrics();
ExceptionHelper.init(getApplicationContext());
- new EmojiService(this).init();
+ EmojiInitializationService.execute(this);
this.isCameraFeatureAvailable = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
this.mTheme = findTheme();
setTheme(this.mTheme);
@@ -23,7 +23,6 @@ import eu.siacs.conversations.ui.ConversationFragment;
import eu.siacs.conversations.ui.XmppActivity;
import eu.siacs.conversations.ui.util.AvatarWorkerTask;
import eu.siacs.conversations.ui.util.StyledAttributes;
-import eu.siacs.conversations.utils.EmojiWrapper;
import eu.siacs.conversations.utils.IrregularUnicodeDetector;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.Jid;
@@ -57,7 +56,7 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
if (name instanceof Jid) {
viewHolder.binding.conversationName.setText(IrregularUnicodeDetector.style(activity, (Jid) name));
} else {
- viewHolder.binding.conversationName.setText(EmojiWrapper.transform(name));
+ viewHolder.binding.conversationName.setText(name);
}
if (conversation == ConversationFragment.getConversation(activity)) {
@@ -85,7 +84,7 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
if (draft != null) {
viewHolder.binding.conversationLastmsgImg.setVisibility(View.GONE);
- viewHolder.binding.conversationLastmsg.setText(EmojiWrapper.transform(draft.getMessage()));
+ viewHolder.binding.conversationLastmsg.setText(draft.getMessage());
viewHolder.binding.senderName.setText(R.string.draft);
viewHolder.binding.senderName.setVisibility(View.VISIBLE);
viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.NORMAL);
@@ -128,7 +127,7 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
}
final Pair<CharSequence, Boolean> preview = UIHelper.getMessagePreview(activity, message, viewHolder.binding.conversationLastmsg.getCurrentTextColor());
if (showPreviewText) {
- viewHolder.binding.conversationLastmsg.setText(EmojiWrapper.transform(UIHelper.shorten(preview.first)));
+ viewHolder.binding.conversationLastmsg.setText(UIHelper.shorten(preview.first));
} else {
viewHolder.binding.conversationLastmsgImg.setContentDescription(preview.first);
}
@@ -22,7 +22,6 @@ import eu.siacs.conversations.ui.SettingsActivity;
import eu.siacs.conversations.ui.XmppActivity;
import eu.siacs.conversations.ui.util.AvatarWorkerTask;
import eu.siacs.conversations.ui.util.StyledAttributes;
-import eu.siacs.conversations.utils.EmojiWrapper;
import eu.siacs.conversations.utils.IrregularUnicodeDetector;
import eu.siacs.conversations.xmpp.Jid;
@@ -85,7 +84,7 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
} else {
viewHolder.jid.setVisibility(View.GONE);
}
- viewHolder.name.setText(EmojiWrapper.transform(item.getDisplayName()));
+ viewHolder.name.setText(item.getDisplayName());
AvatarWorkerTask.loadAvatar(item, viewHolder.avatar, R.dimen.avatar);
return view;
}
@@ -34,7 +34,7 @@ import eu.siacs.conversations.ui.util.ViewUtil;
public class MediaAdapter extends RecyclerView.Adapter<MediaAdapter.MediaViewHolder> {
- private static final List<String> DOCUMENT_MIMES = Arrays.asList(
+ public static final List<String> DOCUMENT_MIMES = Arrays.asList(
"application/pdf",
"application/vnd.oasis.opendocument.text",
"application/msword",
@@ -67,7 +67,6 @@ import eu.siacs.conversations.ui.util.QuoteHelper;
import eu.siacs.conversations.ui.util.ViewUtil;
import eu.siacs.conversations.ui.widget.ClickableMovementMethod;
import eu.siacs.conversations.utils.CryptoHelper;
-import eu.siacs.conversations.utils.EmojiWrapper;
import eu.siacs.conversations.utils.Emoticons;
import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.utils.MessageUtils;
@@ -339,7 +338,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
Spannable span = new SpannableString(body);
float size = Emoticons.isEmoji(body) ? 3.0f : 2.0f;
span.setSpan(new RelativeSizeSpan(size), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- viewHolder.messageBody.setText(EmojiWrapper.transform(span));
+ viewHolder.messageBody.setText(span);
}
private void applyQuoteSpan(SpannableStringBuilder body, int start, int end, boolean darkBackground) {
@@ -510,7 +509,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
}
}
viewHolder.messageBody.setAutoLinkMask(0);
- viewHolder.messageBody.setText(EmojiWrapper.transform(body));
+ viewHolder.messageBody.setText(body);
viewHolder.messageBody.setMovementMethod(ClickableMovementMethod.getInstance());
} else {
viewHolder.messageBody.setText("");
@@ -37,9 +37,10 @@ public class ViewUtil {
view(context, file, mime);
}
- public static void view(Context context, File file, String mime) {
- Intent openIntent = new Intent(Intent.ACTION_VIEW);
- Uri uri;
+ private static void view(Context context, File file, String mime) {
+ Log.d(Config.LOGTAG,"viewing "+file.getAbsolutePath()+" "+mime);
+ final Intent openIntent = new Intent(Intent.ACTION_VIEW);
+ final Uri uri;
try {
uri = FileBackend.getUriForFile(context, file);
} catch (SecurityException e) {
@@ -49,14 +50,9 @@ public class ViewUtil {
}
openIntent.setDataAndType(uri, mime);
openIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- PackageManager manager = context.getPackageManager();
- List<ResolveInfo> info = manager.queryIntentActivities(openIntent, 0);
- if (info.size() == 0) {
- openIntent.setDataAndType(uri, "*/*");
- }
try {
context.startActivity(openIntent);
- } catch (ActivityNotFoundException e) {
+ } catch (final ActivityNotFoundException e) {
Toast.makeText(context, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT).show();
}
}
@@ -15,6 +15,7 @@ import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
+import androidx.appcompat.widget.AppCompatEditText;
import androidx.core.view.inputmethod.EditorInfoCompat;
import androidx.core.view.inputmethod.InputConnectionCompat;
import androidx.core.view.inputmethod.InputContentInfoCompat;
@@ -26,7 +27,7 @@ import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.ui.util.QuoteHelper;
-public class EditMessage extends EmojiWrapperEditText {
+public class EditMessage extends AppCompatEditText {
private static final InputFilter SPAN_FILTER = (source, start, end, dest, dstart, dend) -> source instanceof Spanned ? source.toString() : source;
private final ExecutorService executor = Executors.newSingleThreadExecutor();
@@ -36,7 +36,7 @@ public final class CryptoHelper {
public static final Pattern UUID_PATTERN = Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}");
final public static byte[] ONE = new byte[]{0, 0, 0, 1};
private static final char[] CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz123456789+-/#$!?".toCharArray();
- private static final int PW_LENGTH = 10;
+ private static final int PW_LENGTH = 12;
private static final char[] VOWELS = "aeiou".toCharArray();
private static final char[] CONSONANTS = "bcfghjklmnpqrstvwxyz".toCharArray();
private final static char[] hexArray = "0123456789abcdef".toCharArray();
@@ -1,4 +1,14 @@
package eu.siacs.conversations.utils;
+import java.io.File;
+
public class FileWriterException extends Exception {
+
+ public FileWriterException(File file) {
+ super(String.format("Could not write to %s", file.getAbsolutePath()));
+ }
+
+ FileWriterException() {
+
+ }
}
@@ -568,11 +568,15 @@ public final class MimeUtils {
}
private static String getDisplayName(final Context context, final Uri uri) {
- try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) {
+ try (final Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) {
if (cursor != null && cursor.moveToFirst()) {
- return cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
+ final int index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
+ if (index == -1) {
+ return null;
+ }
+ return cursor.getString(index);
}
- } catch (Exception e) {
+ } catch (final Exception e) {
return null;
}
return null;
@@ -1,5 +1,7 @@
package eu.siacs.conversations.xmpp.jingle;
+import androidx.annotation.NonNull;
+
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
@@ -110,6 +112,7 @@ public abstract class AbstractJingleConnection {
}
@Override
+ @NonNull
public String toString() {
return MoreObjects.toStringHelper(this)
.add("account", account.getJid())
@@ -52,14 +52,16 @@ import eu.siacs.conversations.xmpp.stanzas.IqPacket;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
public class JingleConnectionManager extends AbstractConnectionManager {
- static final ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE = Executors.newSingleThreadScheduledExecutor();
+ static final ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE =
+ Executors.newSingleThreadScheduledExecutor();
final ToneManager toneManager;
- private final HashMap<RtpSessionProposal, DeviceDiscoveryState> rtpSessionProposals = new HashMap<>();
- private final ConcurrentHashMap<AbstractJingleConnection.Id, AbstractJingleConnection> connections = new ConcurrentHashMap<>();
+ private final HashMap<RtpSessionProposal, DeviceDiscoveryState> rtpSessionProposals =
+ new HashMap<>();
+ private final ConcurrentHashMap<AbstractJingleConnection.Id, AbstractJingleConnection>
+ connections = new ConcurrentHashMap<>();
- private final Cache<PersistableSessionId, TerminatedRtpSession> terminatedSessions = CacheBuilder.newBuilder()
- .expireAfterWrite(24, TimeUnit.HOURS)
- .build();
+ private final Cache<PersistableSessionId, TerminatedRtpSession> terminatedSessions =
+ CacheBuilder.newBuilder().expireAfterWrite(24, TimeUnit.HOURS).build();
private final HashMap<Jid, JingleCandidate> primaryCandidates = new HashMap<>();
@@ -87,17 +89,31 @@ public class JingleConnectionManager extends AbstractConnectionManager {
} else if (packet.getAction() == JinglePacket.Action.SESSION_INITIATE) {
final Jid from = packet.getFrom();
final Content content = packet.getJingleContent();
- final String descriptionNamespace = content == null ? null : content.getDescriptionNamespace();
+ final String descriptionNamespace =
+ content == null ? null : content.getDescriptionNamespace();
final AbstractJingleConnection connection;
if (FileTransferDescription.NAMESPACES.contains(descriptionNamespace)) {
connection = new JingleFileTransferConnection(this, id, from);
- } else if (Namespace.JINGLE_APPS_RTP.equals(descriptionNamespace) && isUsingClearNet(account)) {
- final boolean sessionEnded = this.terminatedSessions.asMap().containsKey(PersistableSessionId.of(id));
- final boolean stranger = isWithStrangerAndStrangerNotificationsAreOff(account, id.with);
+ } else if (Namespace.JINGLE_APPS_RTP.equals(descriptionNamespace)
+ && isUsingClearNet(account)) {
+ final boolean sessionEnded =
+ this.terminatedSessions.asMap().containsKey(PersistableSessionId.of(id));
+ final boolean stranger =
+ isWithStrangerAndStrangerNotificationsAreOff(account, id.with);
if (isBusy() != null || sessionEnded || stranger) {
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": rejected session with " + id.with + " because busy. sessionEnded=" + sessionEnded + ", stranger=" + stranger);
- mXmppConnectionService.sendIqPacket(account, packet.generateResponse(IqPacket.TYPE.RESULT), null);
- final JinglePacket sessionTermination = new JinglePacket(JinglePacket.Action.SESSION_TERMINATE, id.sessionId);
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid()
+ + ": rejected session with "
+ + id.with
+ + " because busy. sessionEnded="
+ + sessionEnded
+ + ", stranger="
+ + stranger);
+ mXmppConnectionService.sendIqPacket(
+ account, packet.generateResponse(IqPacket.TYPE.RESULT), null);
+ final JinglePacket sessionTermination =
+ new JinglePacket(JinglePacket.Action.SESSION_TERMINATE, id.sessionId);
sessionTermination.setTo(id.with);
sessionTermination.setReason(Reason.BUSY, null);
mXmppConnectionService.sendIqPacket(account, sessionTermination, null);
@@ -105,7 +121,8 @@ public class JingleConnectionManager extends AbstractConnectionManager {
}
connection = new JingleRtpConnection(this, id, from);
} else {
- respondWithJingleError(account, packet, "unsupported-info", "feature-not-implemented", "cancel");
+ respondWithJingleError(
+ account, packet, "unsupported-info", "feature-not-implemented", "cancel");
return;
}
connections.put(id, connection);
@@ -153,14 +170,17 @@ public class JingleConnectionManager extends AbstractConnectionManager {
}
}
- private Optional<RtpSessionProposal> findMatchingSessionProposal(final Account account, final Jid with, final Set<Media> media) {
+ private Optional<RtpSessionProposal> findMatchingSessionProposal(
+ final Account account, final Jid with, final Set<Media> media) {
synchronized (this.rtpSessionProposals) {
- for (Map.Entry<RtpSessionProposal, DeviceDiscoveryState> entry : this.rtpSessionProposals.entrySet()) {
+ for (Map.Entry<RtpSessionProposal, DeviceDiscoveryState> entry :
+ this.rtpSessionProposals.entrySet()) {
final RtpSessionProposal proposal = entry.getKey();
final DeviceDiscoveryState state = entry.getValue();
- final boolean openProposal = state == DeviceDiscoveryState.DISCOVERED
- || state == DeviceDiscoveryState.SEARCHING
- || state == DeviceDiscoveryState.SEARCHING_ACKNOWLEDGED;
+ final boolean openProposal =
+ state == DeviceDiscoveryState.DISCOVERED
+ || state == DeviceDiscoveryState.SEARCHING
+ || state == DeviceDiscoveryState.SEARCHING_ACKNOWLEDGED;
if (openProposal
&& proposal.account == account
&& proposal.with.equals(with.asBareJid())
@@ -190,7 +210,8 @@ public class JingleConnectionManager extends AbstractConnectionManager {
}
private boolean isWithStrangerAndStrangerNotificationsAreOff(final Account account, Jid with) {
- final boolean notifyForStrangers = mXmppConnectionService.getNotificationService().notificationsFromStrangers();
+ final boolean notifyForStrangers =
+ mXmppConnectionService.getNotificationService().notificationsFromStrangers();
if (notifyForStrangers) {
return false;
}
@@ -198,11 +219,17 @@ public class JingleConnectionManager extends AbstractConnectionManager {
return !contact.showInContactList();
}
- ScheduledFuture<?> schedule(final Runnable runnable, final long delay, final TimeUnit timeUnit) {
+ ScheduledFuture<?> schedule(
+ final Runnable runnable, final long delay, final TimeUnit timeUnit) {
return SCHEDULED_EXECUTOR_SERVICE.schedule(runnable, delay, timeUnit);
}
- void respondWithJingleError(final Account account, final IqPacket original, String jingleCondition, String condition, String conditionType) {
+ void respondWithJingleError(
+ final Account account,
+ final IqPacket original,
+ String jingleCondition,
+ String condition,
+ String conditionType) {
final IqPacket response = original.generateResponse(IqPacket.TYPE.ERROR);
final Element error = response.addChild("error");
error.setAttribute("type", conditionType);
@@ -211,7 +238,14 @@ public class JingleConnectionManager extends AbstractConnectionManager {
account.getXmppConnection().sendIqPacket(response, null);
}
- public void deliverMessage(final Account account, final Jid to, final Jid from, final Element message, String remoteMsgId, String serverMsgId, long timestamp) {
+ public void deliverMessage(
+ final Account account,
+ final Jid to,
+ final Jid from,
+ final Element message,
+ String remoteMsgId,
+ String serverMsgId,
+ long timestamp) {
Preconditions.checkArgument(Namespace.JINGLE_MESSAGE.equals(message.getNamespace()));
final String sessionId = message.getAttribute("id");
if (sessionId == null) {
@@ -245,16 +279,24 @@ public class JingleConnectionManager extends AbstractConnectionManager {
final AbstractJingleConnection existingJingleConnection = connections.get(id);
if (existingJingleConnection != null) {
if (existingJingleConnection instanceof JingleRtpConnection) {
- ((JingleRtpConnection) existingJingleConnection).deliveryMessage(from, message, serverMsgId, timestamp);
+ ((JingleRtpConnection) existingJingleConnection)
+ .deliveryMessage(from, message, serverMsgId, timestamp);
} else {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": " + existingJingleConnection.getClass().getName() + " does not support jingle messages");
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": "
+ + existingJingleConnection.getClass().getName()
+ + " does not support jingle messages");
}
return;
}
if (fromSelf) {
if ("proceed".equals(message.getName())) {
- final Conversation c = mXmppConnectionService.findOrCreateConversation(account, id.with, false, false);
+ final Conversation c =
+ mXmppConnectionService.findOrCreateConversation(
+ account, id.with, false, false);
final Message previousBusy = c.findRtpSession(sessionId, Message.STATUS_RECEIVED);
if (previousBusy != null) {
previousBusy.setBody(new RtpSessionStatus(true, 0).toString());
@@ -263,45 +305,72 @@ public class JingleConnectionManager extends AbstractConnectionManager {
}
previousBusy.setTime(timestamp);
mXmppConnectionService.updateMessage(previousBusy, true);
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": updated previous busy because call got picked up by another device");
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid()
+ + ": updated previous busy because call got picked up by another device");
return;
}
}
- //TODO handle reject for cases where we don’t have carbon copies (normally reject is to be sent to own bare jid as well)
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignore jingle message from self");
+ // TODO handle reject for cases where we don’t have carbon copies (normally reject is to
+ // be sent to own bare jid as well)
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid() + ": ignore jingle message from self");
return;
}
if ("propose".equals(message.getName())) {
final Propose propose = Propose.upgrade(message);
final List<GenericDescription> descriptions = propose.getDescriptions();
- final Collection<RtpDescription> rtpDescriptions = Collections2.transform(
- Collections2.filter(descriptions, d -> d instanceof RtpDescription),
- input -> (RtpDescription) input
- );
- if (rtpDescriptions.size() > 0 && rtpDescriptions.size() == descriptions.size() && isUsingClearNet(account)) {
- final Collection<Media> media = Collections2.transform(rtpDescriptions, RtpDescription::getMedia);
+ final Collection<RtpDescription> rtpDescriptions =
+ Collections2.transform(
+ Collections2.filter(descriptions, d -> d instanceof RtpDescription),
+ input -> (RtpDescription) input);
+ if (rtpDescriptions.size() > 0
+ && rtpDescriptions.size() == descriptions.size()
+ && isUsingClearNet(account)) {
+ final Collection<Media> media =
+ Collections2.transform(rtpDescriptions, RtpDescription::getMedia);
if (media.contains(Media.UNKNOWN)) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": encountered unknown media in session proposal. " + propose);
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": encountered unknown media in session proposal. "
+ + propose);
return;
}
- final Optional<RtpSessionProposal> matchingSessionProposal = findMatchingSessionProposal(account, id.with, ImmutableSet.copyOf(media));
+ final Optional<RtpSessionProposal> matchingSessionProposal =
+ findMatchingSessionProposal(account, id.with, ImmutableSet.copyOf(media));
if (matchingSessionProposal.isPresent()) {
final String ourSessionId = matchingSessionProposal.get().sessionId;
final String theirSessionId = id.sessionId;
if (ComparisonChain.start()
- .compare(ourSessionId, theirSessionId)
- .compare(account.getJid().toEscapedString(), id.with.toEscapedString())
- .result() > 0) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": our session lost tie break. automatically accepting their session. winning Session=" + theirSessionId);
- //TODO a retract for this reason should probably include some indication of tie break
+ .compare(ourSessionId, theirSessionId)
+ .compare(
+ account.getJid().toEscapedString(),
+ id.with.toEscapedString())
+ .result()
+ > 0) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": our session lost tie break. automatically accepting their session. winning Session="
+ + theirSessionId);
+ // TODO a retract for this reason should probably include some indication of
+ // tie break
retractSessionProposal(matchingSessionProposal.get());
- final JingleRtpConnection rtpConnection = new JingleRtpConnection(this, id, from);
+ final JingleRtpConnection rtpConnection =
+ new JingleRtpConnection(this, id, from);
this.connections.put(id, rtpConnection);
rtpConnection.setProposedMedia(ImmutableSet.copyOf(media));
rtpConnection.deliveryMessage(from, message, serverMsgId, timestamp);
} else {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": our session won tie break. waiting for other party to accept. winningSession=" + ourSessionId);
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": our session won tie break. waiting for other party to accept. winningSession="
+ + ourSessionId);
}
return;
}
@@ -309,38 +378,63 @@ public class JingleConnectionManager extends AbstractConnectionManager {
if (isBusy() != null || stranger) {
writeLogMissedIncoming(account, id.with.asBareJid(), id.sessionId, serverMsgId, timestamp);
if (stranger) {
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": ignoring call proposal from stranger " + id.with);
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid()
+ + ": ignoring call proposal from stranger "
+ + id.with);
return;
}
final int activeDevices = account.activeDevicesWithRtpCapability();
Log.d(Config.LOGTAG, "active devices with rtp capability: " + activeDevices);
if (activeDevices == 0) {
- final MessagePacket reject = mXmppConnectionService.getMessageGenerator().sessionReject(from, sessionId);
+ final MessagePacket reject =
+ mXmppConnectionService
+ .getMessageGenerator()
+ .sessionReject(from, sessionId);
mXmppConnectionService.sendMessagePacket(account, reject);
} else {
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": ignoring proposal because busy on this device but there are other devices");
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid()
+ + ": ignoring proposal because busy on this device but there are other devices");
}
} else {
- final JingleRtpConnection rtpConnection = new JingleRtpConnection(this, id, from);
+ final JingleRtpConnection rtpConnection =
+ new JingleRtpConnection(this, id, from);
this.connections.put(id, rtpConnection);
rtpConnection.setProposedMedia(ImmutableSet.copyOf(media));
rtpConnection.deliveryMessage(from, message, serverMsgId, timestamp);
}
} else {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to react to proposed session with " + rtpDescriptions.size() + " rtp descriptions of " + descriptions.size() + " total descriptions");
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": unable to react to proposed session with "
+ + rtpDescriptions.size()
+ + " rtp descriptions of "
+ + descriptions.size()
+ + " total descriptions");
}
} else if (addressedDirectly && "proceed".equals(message.getName())) {
synchronized (rtpSessionProposals) {
- final RtpSessionProposal proposal = getRtpSessionProposal(account, from.asBareJid(), sessionId);
+ final RtpSessionProposal proposal =
+ getRtpSessionProposal(account, from.asBareJid(), sessionId);
if (proposal != null) {
rtpSessionProposals.remove(proposal);
- final JingleRtpConnection rtpConnection = new JingleRtpConnection(this, id, account.getJid());
+ final JingleRtpConnection rtpConnection =
+ new JingleRtpConnection(this, id, account.getJid());
rtpConnection.setProposedMedia(proposal.media);
this.connections.put(id, rtpConnection);
rtpConnection.transitionOrThrow(AbstractJingleConnection.State.PROPOSED);
rtpConnection.deliveryMessage(from, message, serverMsgId, timestamp);
} else {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": no rtp session proposal found for " + from + " to deliver proceed");
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": no rtp session proposal found for "
+ + from
+ + " to deliver proceed");
if (remoteMsgId == null) {
return;
}
@@ -356,63 +450,77 @@ public class JingleConnectionManager extends AbstractConnectionManager {
}
}
} else if (addressedDirectly && "reject".equals(message.getName())) {
- final RtpSessionProposal proposal = getRtpSessionProposal(account, from.asBareJid(), sessionId);
+ final RtpSessionProposal proposal =
+ getRtpSessionProposal(account, from.asBareJid(), sessionId);
synchronized (rtpSessionProposals) {
if (proposal != null && rtpSessionProposals.remove(proposal) != null) {
- writeLogMissedOutgoing(account, proposal.with, proposal.sessionId, serverMsgId, timestamp);
+ writeLogMissedOutgoing(
+ account, proposal.with, proposal.sessionId, serverMsgId, timestamp);
toneManager.transition(RtpEndUserState.DECLINED_OR_BUSY, proposal.media);
- mXmppConnectionService.notifyJingleRtpConnectionUpdate(account, proposal.with, proposal.sessionId, RtpEndUserState.DECLINED_OR_BUSY);
+ mXmppConnectionService.notifyJingleRtpConnectionUpdate(
+ account,
+ proposal.with,
+ proposal.sessionId,
+ RtpEndUserState.DECLINED_OR_BUSY);
} else {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": no rtp session proposal found for " + from + " to deliver reject");
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": no rtp session proposal found for "
+ + from
+ + " to deliver reject");
}
}
} else {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved out of order jingle message" + message);
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": retrieved out of order jingle message"
+ + message);
}
-
}
- private RtpSessionProposal getRtpSessionProposal(final Account account, Jid from, String sessionId) {
+ private RtpSessionProposal getRtpSessionProposal(
+ final Account account, Jid from, String sessionId) {
for (RtpSessionProposal rtpSessionProposal : rtpSessionProposals.keySet()) {
- if (rtpSessionProposal.sessionId.equals(sessionId) && rtpSessionProposal.with.equals(from) && rtpSessionProposal.account.getJid().equals(account.getJid())) {
+ if (rtpSessionProposal.sessionId.equals(sessionId)
+ && rtpSessionProposal.with.equals(from)
+ && rtpSessionProposal.account.getJid().equals(account.getJid())) {
return rtpSessionProposal;
}
}
return null;
}
- private void writeLogMissedOutgoing(final Account account, Jid with, final String sessionId, String serverMsgId, long timestamp) {
- final Conversation conversation = mXmppConnectionService.findOrCreateConversation(
- account,
- with.asBareJid(),
- false,
- false
- );
- final Message message = new Message(
- conversation,
- Message.STATUS_SEND,
- Message.TYPE_RTP_SESSION,
- sessionId
- );
+ private void writeLogMissedOutgoing(
+ final Account account,
+ Jid with,
+ final String sessionId,
+ String serverMsgId,
+ long timestamp) {
+ final Conversation conversation =
+ mXmppConnectionService.findOrCreateConversation(
+ account, with.asBareJid(), false, false);
+ final Message message =
+ new Message(conversation, Message.STATUS_SEND, Message.TYPE_RTP_SESSION, sessionId);
message.setBody(new RtpSessionStatus(false, 0).toString());
message.setServerMsgId(serverMsgId);
message.setTime(timestamp);
writeMessage(message);
}
- private void writeLogMissedIncoming(final Account account, Jid with, final String sessionId, String serverMsgId, long timestamp) {
- final Conversation conversation = mXmppConnectionService.findOrCreateConversation(
- account,
- with.asBareJid(),
- false,
- false
- );
- final Message message = new Message(
- conversation,
- Message.STATUS_RECEIVED,
- Message.TYPE_RTP_SESSION,
- sessionId
- );
+ private void writeLogMissedIncoming(
+ final Account account,
+ Jid with,
+ final String sessionId,
+ String serverMsgId,
+ long timestamp) {
+ final Conversation conversation =
+ mXmppConnectionService.findOrCreateConversation(
+ account, with.asBareJid(), false, false);
+ final Message message =
+ new Message(
+ conversation, Message.STATUS_RECEIVED, Message.TYPE_RTP_SESSION, sessionId);
message.setBody(new RtpSessionStatus(false, 0).toString());
message.setServerMsgId(serverMsgId);
message.setTime(timestamp);
@@ -431,34 +539,41 @@ public class JingleConnectionManager extends AbstractConnectionManager {
}
public void startJingleFileTransfer(final Message message) {
- Preconditions.checkArgument(message.isFileOrImage(), "Message is not of type file or image");
+ Preconditions.checkArgument(
+ message.isFileOrImage(), "Message is not of type file or image");
final Transferable old = message.getTransferable();
if (old != null) {
old.cancel();
}
final Account account = message.getConversation().getAccount();
final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(message);
- final JingleFileTransferConnection connection = new JingleFileTransferConnection(this, id, account.getJid());
+ final JingleFileTransferConnection connection =
+ new JingleFileTransferConnection(this, id, account.getJid());
mXmppConnectionService.markMessage(message, Message.STATUS_WAITING);
this.connections.put(id, connection);
connection.init(message);
}
public Optional<OngoingRtpSession> getOngoingRtpConnection(final Contact contact) {
- for (final Map.Entry<AbstractJingleConnection.Id, AbstractJingleConnection> entry : this.connections.entrySet()) {
+ for (final Map.Entry<AbstractJingleConnection.Id, AbstractJingleConnection> entry :
+ this.connections.entrySet()) {
if (entry.getValue() instanceof JingleRtpConnection) {
final AbstractJingleConnection.Id id = entry.getKey();
- if (id.account == contact.getAccount() && id.with.asBareJid().equals(contact.getJid().asBareJid())) {
+ if (id.account == contact.getAccount()
+ && id.with.asBareJid().equals(contact.getJid().asBareJid())) {
return Optional.of(id);
}
}
}
synchronized (this.rtpSessionProposals) {
- for (Map.Entry<RtpSessionProposal, DeviceDiscoveryState> entry : this.rtpSessionProposals.entrySet()) {
+ for (Map.Entry<RtpSessionProposal, DeviceDiscoveryState> entry :
+ this.rtpSessionProposals.entrySet()) {
RtpSessionProposal proposal = entry.getKey();
- if (proposal.account == contact.getAccount() && contact.getJid().asBareJid().equals(proposal.with)) {
+ if (proposal.account == contact.getAccount()
+ && contact.getJid().asBareJid().equals(proposal.with)) {
final DeviceDiscoveryState preexistingState = entry.getValue();
- if (preexistingState != null && preexistingState != DeviceDiscoveryState.FAILED) {
+ if (preexistingState != null
+ && preexistingState != DeviceDiscoveryState.FAILED) {
return Optional.of(proposal);
}
}
@@ -474,7 +589,8 @@ public class JingleConnectionManager extends AbstractConnectionManager {
void finishConnectionOrThrow(final AbstractJingleConnection connection) {
final AbstractJingleConnection.Id id = connection.getId();
if (this.connections.remove(id) == null) {
- throw new IllegalStateException(String.format("Unable to finish connection with id=%s", id.toString()));
+ throw new IllegalStateException(
+ String.format("Unable to finish connection with id=%s", id.toString()));
}
}
@@ -493,49 +609,70 @@ public class JingleConnectionManager extends AbstractConnectionManager {
return firedUpdates;
}
- void getPrimaryCandidate(final Account account, final boolean initiator, final OnPrimaryCandidateFound listener) {
+ void getPrimaryCandidate(
+ final Account account,
+ final boolean initiator,
+ final OnPrimaryCandidateFound listener) {
if (Config.DISABLE_PROXY_LOOKUP) {
listener.onPrimaryCandidateFound(false, null);
return;
}
if (!this.primaryCandidates.containsKey(account.getJid().asBareJid())) {
- final Jid proxy = account.getXmppConnection().findDiscoItemByFeature(Namespace.BYTE_STREAMS);
+ final Jid proxy =
+ account.getXmppConnection().findDiscoItemByFeature(Namespace.BYTE_STREAMS);
if (proxy != null) {
IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
iq.setTo(proxy);
iq.query(Namespace.BYTE_STREAMS);
- account.getXmppConnection().sendIqPacket(iq, new OnIqPacketReceived() {
-
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- final Element streamhost = packet.query().findChild("streamhost", Namespace.BYTE_STREAMS);
- final String host = streamhost == null ? null : streamhost.getAttribute("host");
- final String port = streamhost == null ? null : streamhost.getAttribute("port");
- if (host != null && port != null) {
- try {
- JingleCandidate candidate = new JingleCandidate(nextRandomId(), true);
- candidate.setHost(host);
- candidate.setPort(Integer.parseInt(port));
- candidate.setType(JingleCandidate.TYPE_PROXY);
- candidate.setJid(proxy);
- candidate.setPriority(655360 + (initiator ? 30 : 0));
- primaryCandidates.put(account.getJid().asBareJid(), candidate);
- listener.onPrimaryCandidateFound(true, candidate);
- } catch (final NumberFormatException e) {
- listener.onPrimaryCandidateFound(false, null);
- }
- } else {
- listener.onPrimaryCandidateFound(false, null);
- }
- }
- });
+ account.getXmppConnection()
+ .sendIqPacket(
+ iq,
+ new OnIqPacketReceived() {
+
+ @Override
+ public void onIqPacketReceived(
+ Account account, IqPacket packet) {
+ final Element streamhost =
+ packet.query()
+ .findChild(
+ "streamhost",
+ Namespace.BYTE_STREAMS);
+ final String host =
+ streamhost == null
+ ? null
+ : streamhost.getAttribute("host");
+ final String port =
+ streamhost == null
+ ? null
+ : streamhost.getAttribute("port");
+ if (host != null && port != null) {
+ try {
+ JingleCandidate candidate =
+ new JingleCandidate(nextRandomId(), true);
+ candidate.setHost(host);
+ candidate.setPort(Integer.parseInt(port));
+ candidate.setType(JingleCandidate.TYPE_PROXY);
+ candidate.setJid(proxy);
+ candidate.setPriority(
+ 655360 + (initiator ? 30 : 0));
+ primaryCandidates.put(
+ account.getJid().asBareJid(), candidate);
+ listener.onPrimaryCandidateFound(true, candidate);
+ } catch (final NumberFormatException e) {
+ listener.onPrimaryCandidateFound(false, null);
+ }
+ } else {
+ listener.onPrimaryCandidateFound(false, null);
+ }
+ }
+ });
} else {
listener.onPrimaryCandidateFound(false, null);
}
} else {
- listener.onPrimaryCandidateFound(true,
- this.primaryCandidates.get(account.getJid().asBareJid()));
+ listener.onPrimaryCandidateFound(
+ true, this.primaryCandidates.get(account.getJid().asBareJid()));
}
}
@@ -557,16 +694,28 @@ public class JingleConnectionManager extends AbstractConnectionManager {
private void retractSessionProposal(RtpSessionProposal rtpSessionProposal) {
final Account account = rtpSessionProposal.account;
toneManager.transition(RtpEndUserState.ENDED, rtpSessionProposal.media);
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retracting rtp session proposal with " + rtpSessionProposal.with);
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": retracting rtp session proposal with "
+ + rtpSessionProposal.with);
this.rtpSessionProposals.remove(rtpSessionProposal);
- final MessagePacket messagePacket = mXmppConnectionService.getMessageGenerator().sessionRetract(rtpSessionProposal);
- writeLogMissedOutgoing(account, rtpSessionProposal.with, rtpSessionProposal.sessionId, null, System.currentTimeMillis());
+ final MessagePacket messagePacket =
+ mXmppConnectionService.getMessageGenerator().sessionRetract(rtpSessionProposal);
+ writeLogMissedOutgoing(
+ account,
+ rtpSessionProposal.with,
+ rtpSessionProposal.sessionId,
+ null,
+ System.currentTimeMillis());
mXmppConnectionService.sendMessagePacket(account, messagePacket);
}
- public String initializeRtpSession(final Account account, final Jid with, final Set<Media> media) {
+ public String initializeRtpSession(
+ final Account account, final Jid with, final Set<Media> media) {
final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(account, with);
- final JingleRtpConnection rtpConnection = new JingleRtpConnection(this, id, account.getJid());
+ final JingleRtpConnection rtpConnection =
+ new JingleRtpConnection(this, id, account.getJid());
rtpConnection.setProposedMedia(media);
this.connections.put(id, rtpConnection);
rtpConnection.sendSessionInitiate();
@@ -575,11 +724,13 @@ public class JingleConnectionManager extends AbstractConnectionManager {
public String proposeJingleRtpSession(final Account account, final Jid with, final Set<Media> media) {
synchronized (this.rtpSessionProposals) {
- for (Map.Entry<RtpSessionProposal, DeviceDiscoveryState> entry : this.rtpSessionProposals.entrySet()) {
+ for (Map.Entry<RtpSessionProposal, DeviceDiscoveryState> entry :
+ this.rtpSessionProposals.entrySet()) {
RtpSessionProposal proposal = entry.getKey();
if (proposal.account == account && with.asBareJid().equals(proposal.with)) {
final DeviceDiscoveryState preexistingState = entry.getValue();
- if (preexistingState != null && preexistingState != DeviceDiscoveryState.FAILED) {
+ if (preexistingState != null
+ && preexistingState != DeviceDiscoveryState.FAILED) {
final RtpEndUserState endUserState = preexistingState.toEndUserState();
toneManager.transition(endUserState, media);
mXmppConnectionService.notifyJingleRtpConnectionUpdate(
@@ -601,15 +752,13 @@ public class JingleConnectionManager extends AbstractConnectionManager {
}
throw new IllegalStateException("There is already a running RTP session: " + busyCode);
}
- final RtpSessionProposal proposal = RtpSessionProposal.of(account, with.asBareJid(), media);
+ final RtpSessionProposal proposal =
+ RtpSessionProposal.of(account, with.asBareJid(), media);
this.rtpSessionProposals.put(proposal, DeviceDiscoveryState.SEARCHING);
mXmppConnectionService.notifyJingleRtpConnectionUpdate(
- account,
- proposal.with,
- proposal.sessionId,
- RtpEndUserState.FINDING_DEVICE
- );
- final MessagePacket messagePacket = mXmppConnectionService.getMessageGenerator().sessionProposal(proposal);
+ account, proposal.with, proposal.sessionId, RtpEndUserState.FINDING_DEVICE);
+ final MessagePacket messagePacket =
+ mXmppConnectionService.getMessageGenerator().sessionProposal(proposal);
mXmppConnectionService.sendMessagePacket(account, messagePacket);
return proposal.sessionId;
}
@@ -617,7 +766,8 @@ public class JingleConnectionManager extends AbstractConnectionManager {
public boolean hasMatchingProposal(final Account account, final Jid with) {
synchronized (this.rtpSessionProposals) {
- for (Map.Entry<RtpSessionProposal, DeviceDiscoveryState> entry : this.rtpSessionProposals.entrySet()) {
+ for (Map.Entry<RtpSessionProposal, DeviceDiscoveryState> entry :
+ this.rtpSessionProposals.entrySet()) {
final RtpSessionProposal proposal = entry.getKey();
if (proposal.account == account && with.asBareJid().equals(proposal.with)) {
return true;
@@ -646,10 +796,12 @@ public class JingleConnectionManager extends AbstractConnectionManager {
if (sid != null) {
for (final AbstractJingleConnection connection : this.connections.values()) {
if (connection instanceof JingleFileTransferConnection) {
- final JingleFileTransferConnection fileTransfer = (JingleFileTransferConnection) connection;
+ final JingleFileTransferConnection fileTransfer =
+ (JingleFileTransferConnection) connection;
final JingleTransport transport = fileTransfer.getTransport();
if (transport instanceof JingleInBandTransport) {
- final JingleInBandTransport inBandTransport = (JingleInBandTransport) transport;
+ final JingleInBandTransport inBandTransport =
+ (JingleInBandTransport) transport;
if (inBandTransport.matches(account, sid)) {
inBandTransport.deliverPayload(packet, payload);
}
@@ -659,7 +811,8 @@ public class JingleConnectionManager extends AbstractConnectionManager {
}
}
Log.d(Config.LOGTAG, "unable to deliver ibb packet: " + packet.toString());
- account.getXmppConnection().sendIqPacket(packet.generateResponse(IqPacket.TYPE.ERROR), null);
+ account.getXmppConnection()
+ .sendIqPacket(packet.generateResponse(IqPacket.TYPE.ERROR), null);
}
public void notifyRebound(final Account account) {
@@ -672,8 +825,10 @@ public class JingleConnectionManager extends AbstractConnectionManager {
}
}
- public WeakReference<JingleRtpConnection> findJingleRtpConnection(Account account, Jid with, String sessionId) {
- final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(account, with, sessionId);
+ public WeakReference<JingleRtpConnection> findJingleRtpConnection(
+ Account account, Jid with, String sessionId) {
+ final AbstractJingleConnection.Id id =
+ AbstractJingleConnection.Id.of(account, with, sessionId);
final AbstractJingleConnection connection = connections.get(id);
if (connection instanceof JingleRtpConnection) {
return new WeakReference<>((JingleRtpConnection) connection);
@@ -683,34 +838,53 @@ public class JingleConnectionManager extends AbstractConnectionManager {
private void resendSessionProposals(final Account account) {
synchronized (this.rtpSessionProposals) {
- for (final Map.Entry<RtpSessionProposal, DeviceDiscoveryState> entry : this.rtpSessionProposals.entrySet()) {
+ for (final Map.Entry<RtpSessionProposal, DeviceDiscoveryState> entry :
+ this.rtpSessionProposals.entrySet()) {
final RtpSessionProposal proposal = entry.getKey();
- if (entry.getValue() == DeviceDiscoveryState.SEARCHING && proposal.account == account) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": resending session proposal to " + proposal.with);
- final MessagePacket messagePacket = mXmppConnectionService.getMessageGenerator().sessionProposal(proposal);
+ if (entry.getValue() == DeviceDiscoveryState.SEARCHING
+ && proposal.account == account) {
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": resending session proposal to "
+ + proposal.with);
+ final MessagePacket messagePacket =
+ mXmppConnectionService.getMessageGenerator().sessionProposal(proposal);
mXmppConnectionService.sendMessagePacket(account, messagePacket);
}
}
}
}
- public void updateProposedSessionDiscovered(Account account, Jid from, String sessionId, final DeviceDiscoveryState target) {
+ public void updateProposedSessionDiscovered(
+ Account account, Jid from, String sessionId, final DeviceDiscoveryState target) {
synchronized (this.rtpSessionProposals) {
- final RtpSessionProposal sessionProposal = getRtpSessionProposal(account, from.asBareJid(), sessionId);
- final DeviceDiscoveryState currentState = sessionProposal == null ? null : rtpSessionProposals.get(sessionProposal);
+ final RtpSessionProposal sessionProposal =
+ getRtpSessionProposal(account, from.asBareJid(), sessionId);
+ final DeviceDiscoveryState currentState =
+ sessionProposal == null ? null : rtpSessionProposals.get(sessionProposal);
if (currentState == null) {
Log.d(Config.LOGTAG, "unable to find session proposal for session id " + sessionId);
return;
}
if (currentState == DeviceDiscoveryState.DISCOVERED) {
- Log.d(Config.LOGTAG, "session proposal already at discovered. not going to fall back");
+ Log.d(
+ Config.LOGTAG,
+ "session proposal already at discovered. not going to fall back");
return;
}
this.rtpSessionProposals.put(sessionProposal, target);
final RtpEndUserState endUserState = target.toEndUserState();
toneManager.transition(endUserState, sessionProposal.media);
- mXmppConnectionService.notifyJingleRtpConnectionUpdate(account, sessionProposal.with, sessionProposal.sessionId, endUserState);
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": flagging session " + sessionId + " as " + target);
+ mXmppConnectionService.notifyJingleRtpConnectionUpdate(
+ account, sessionProposal.with, sessionProposal.sessionId, endUserState);
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + ": flagging session "
+ + sessionId
+ + " as "
+ + target);
}
}
@@ -735,7 +909,8 @@ public class JingleConnectionManager extends AbstractConnectionManager {
}
public void failProceed(Account account, final Jid with, String sessionId) {
- final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(account, with, sessionId);
+ final AbstractJingleConnection.Id id =
+ AbstractJingleConnection.Id.of(account, with, sessionId);
final AbstractJingleConnection existingJingleConnection = connections.get(id);
if (existingJingleConnection instanceof JingleRtpConnection) {
((JingleRtpConnection) existingJingleConnection).deliverFailedProceed();
@@ -746,13 +921,17 @@ public class JingleConnectionManager extends AbstractConnectionManager {
if (connections.containsValue(connection)) {
return;
}
- final IllegalStateException e = new IllegalStateException("JingleConnection has not been registered with connection manager");
+ final IllegalStateException e =
+ new IllegalStateException(
+ "JingleConnection has not been registered with connection manager");
Log.e(Config.LOGTAG, "ensureConnectionIsRegistered() failed. Going to throw", e);
throw e;
}
- void setTerminalSessionState(AbstractJingleConnection.Id id, final RtpEndUserState state, final Set<Media> media) {
- this.terminatedSessions.put(PersistableSessionId.of(id), new TerminatedRtpSession(state, media));
+ void setTerminalSessionState(
+ AbstractJingleConnection.Id id, final RtpEndUserState state, final Set<Media> media) {
+ this.terminatedSessions.put(
+ PersistableSessionId.of(id), new TerminatedRtpSession(state, media));
}
public TerminatedRtpSession getTerminalSessionState(final Jid with, final String sessionId) {
@@ -777,8 +956,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PersistableSessionId that = (PersistableSessionId) o;
- return Objects.equal(with, that.with) &&
- Objects.equal(sessionId, that.sessionId);
+ return Objects.equal(with, that.with) && Objects.equal(sessionId, that.sessionId);
}
@Override
@@ -798,7 +976,10 @@ public class JingleConnectionManager extends AbstractConnectionManager {
}
public enum DeviceDiscoveryState {
- SEARCHING, SEARCHING_ACKNOWLEDGED, DISCOVERED, FAILED;
+ SEARCHING,
+ SEARCHING_ACKNOWLEDGED,
+ DISCOVERED,
+ FAILED;
public RtpEndUserState toEndUserState() {
switch (this) {
@@ -839,9 +1020,9 @@ public class JingleConnectionManager extends AbstractConnectionManager {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
RtpSessionProposal proposal = (RtpSessionProposal) o;
- return Objects.equal(account.getJid(), proposal.account.getJid()) &&
- Objects.equal(with, proposal.with) &&
- Objects.equal(sessionId, proposal.sessionId);
+ return Objects.equal(account.getJid(), proposal.account.getJid())
+ && Objects.equal(with, proposal.with)
+ && Objects.equal(sessionId, proposal.sessionId);
}
@Override
@@ -492,19 +492,19 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
AbstractConnectionManager.Extension extension = AbstractConnectionManager.Extension.of(path);
if (VALID_IMAGE_EXTENSIONS.contains(extension.main)) {
message.setType(Message.TYPE_IMAGE);
- message.setRelativeFilePath(message.getUuid() + "." + extension.main);
+ xmppConnectionService.getFileBackend().setupRelativeFilePath(message, message.getUuid() + "." + extension.main);
} else if (VALID_CRYPTO_EXTENSIONS.contains(extension.main)) {
if (VALID_IMAGE_EXTENSIONS.contains(extension.secondary)) {
message.setType(Message.TYPE_IMAGE);
- message.setRelativeFilePath(message.getUuid() + "." + extension.secondary);
+ xmppConnectionService.getFileBackend().setupRelativeFilePath(message,message.getUuid() + "." + extension.secondary);
} else {
message.setType(Message.TYPE_FILE);
- message.setRelativeFilePath(message.getUuid() + (extension.secondary != null ? ("." + extension.secondary) : ""));
+ xmppConnectionService.getFileBackend().setupRelativeFilePath(message,message.getUuid() + (extension.secondary != null ? ("." + extension.secondary) : ""));
}
message.setEncryption(Message.ENCRYPTION_PGP);
} else {
message.setType(Message.TYPE_FILE);
- message.setRelativeFilePath(message.getUuid() + (extension.main != null ? ("." + extension.main) : ""));
+ xmppConnectionService.getFileBackend().setupRelativeFilePath(message,message.getUuid() + (extension.main != null ? ("." + extension.main) : ""));
}
long size = parseLong(fileSize, 0);
message.setBody(Long.toString(size));
@@ -62,91 +62,103 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
-public class JingleRtpConnection extends AbstractJingleConnection implements WebRTCWrapper.EventCallback {
+public class JingleRtpConnection extends AbstractJingleConnection
+ implements WebRTCWrapper.EventCallback {
- public static final List<State> STATES_SHOWING_ONGOING_CALL = Arrays.asList(
- State.PROCEED,
- State.SESSION_INITIALIZED_PRE_APPROVED,
- State.SESSION_ACCEPTED
- );
+ public static final List<State> STATES_SHOWING_ONGOING_CALL =
+ Arrays.asList(
+ State.PROCEED, State.SESSION_INITIALIZED_PRE_APPROVED, State.SESSION_ACCEPTED);
private static final long BUSY_TIME_OUT = 30;
- private static final List<State> TERMINATED = Arrays.asList(
- State.ACCEPTED,
- State.REJECTED,
- State.REJECTED_RACED,
- State.RETRACTED,
- State.RETRACTED_RACED,
- State.TERMINATED_SUCCESS,
- State.TERMINATED_DECLINED_OR_BUSY,
- State.TERMINATED_CONNECTIVITY_ERROR,
- State.TERMINATED_CANCEL_OR_TIMEOUT,
- State.TERMINATED_APPLICATION_FAILURE,
- State.TERMINATED_SECURITY_ERROR
- );
+ private static final List<State> TERMINATED =
+ Arrays.asList(
+ State.ACCEPTED,
+ State.REJECTED,
+ State.REJECTED_RACED,
+ State.RETRACTED,
+ State.RETRACTED_RACED,
+ State.TERMINATED_SUCCESS,
+ State.TERMINATED_DECLINED_OR_BUSY,
+ State.TERMINATED_CONNECTIVITY_ERROR,
+ State.TERMINATED_CANCEL_OR_TIMEOUT,
+ State.TERMINATED_APPLICATION_FAILURE,
+ State.TERMINATED_SECURITY_ERROR);
private static final Map<State, Collection<State>> VALID_TRANSITIONS;
static {
- final ImmutableMap.Builder<State, Collection<State>> transitionBuilder = new ImmutableMap.Builder<>();
- transitionBuilder.put(State.NULL, ImmutableList.of(
+ final ImmutableMap.Builder<State, Collection<State>> transitionBuilder =
+ new ImmutableMap.Builder<>();
+ transitionBuilder.put(
+ State.NULL,
+ ImmutableList.of(
+ State.PROPOSED,
+ State.SESSION_INITIALIZED,
+ State.TERMINATED_APPLICATION_FAILURE,
+ State.TERMINATED_SECURITY_ERROR));
+ transitionBuilder.put(
State.PROPOSED,
- State.SESSION_INITIALIZED,
- State.TERMINATED_APPLICATION_FAILURE,
- State.TERMINATED_SECURITY_ERROR
- ));
- transitionBuilder.put(State.PROPOSED, ImmutableList.of(
- State.ACCEPTED,
+ ImmutableList.of(
+ State.ACCEPTED,
+ State.PROCEED,
+ State.REJECTED,
+ State.RETRACTED,
+ State.TERMINATED_APPLICATION_FAILURE,
+ State.TERMINATED_SECURITY_ERROR,
+ State.TERMINATED_CONNECTIVITY_ERROR // only used when the xmpp connection
+ // rebinds
+ ));
+ transitionBuilder.put(
State.PROCEED,
- State.REJECTED,
- State.RETRACTED,
- State.TERMINATED_APPLICATION_FAILURE,
- State.TERMINATED_SECURITY_ERROR,
- State.TERMINATED_CONNECTIVITY_ERROR //only used when the xmpp connection rebinds
- ));
- transitionBuilder.put(State.PROCEED, ImmutableList.of(
- State.REJECTED_RACED,
- State.RETRACTED_RACED,
+ ImmutableList.of(
+ State.REJECTED_RACED,
+ State.RETRACTED_RACED,
+ State.SESSION_INITIALIZED_PRE_APPROVED,
+ State.TERMINATED_SUCCESS,
+ State.TERMINATED_APPLICATION_FAILURE,
+ State.TERMINATED_SECURITY_ERROR,
+ State.TERMINATED_CONNECTIVITY_ERROR // at this state used for error
+ // bounces of the proceed message
+ ));
+ transitionBuilder.put(
+ State.SESSION_INITIALIZED,
+ ImmutableList.of(
+ State.SESSION_ACCEPTED,
+ State.TERMINATED_SUCCESS,
+ State.TERMINATED_DECLINED_OR_BUSY,
+ State.TERMINATED_CONNECTIVITY_ERROR, // at this state used for IQ errors
+ // and IQ timeouts
+ State.TERMINATED_CANCEL_OR_TIMEOUT,
+ State.TERMINATED_APPLICATION_FAILURE,
+ State.TERMINATED_SECURITY_ERROR));
+ transitionBuilder.put(
State.SESSION_INITIALIZED_PRE_APPROVED,
- State.TERMINATED_SUCCESS,
- State.TERMINATED_APPLICATION_FAILURE,
- State.TERMINATED_SECURITY_ERROR,
- State.TERMINATED_CONNECTIVITY_ERROR //at this state used for error bounces of the proceed message
- ));
- transitionBuilder.put(State.SESSION_INITIALIZED, ImmutableList.of(
+ ImmutableList.of(
+ State.SESSION_ACCEPTED,
+ State.TERMINATED_SUCCESS,
+ State.TERMINATED_DECLINED_OR_BUSY,
+ State.TERMINATED_CONNECTIVITY_ERROR, // at this state used for IQ errors
+ // and IQ timeouts
+ State.TERMINATED_CANCEL_OR_TIMEOUT,
+ State.TERMINATED_APPLICATION_FAILURE,
+ State.TERMINATED_SECURITY_ERROR));
+ transitionBuilder.put(
State.SESSION_ACCEPTED,
- State.TERMINATED_SUCCESS,
- State.TERMINATED_DECLINED_OR_BUSY,
- State.TERMINATED_CONNECTIVITY_ERROR, //at this state used for IQ errors and IQ timeouts
- State.TERMINATED_CANCEL_OR_TIMEOUT,
- State.TERMINATED_APPLICATION_FAILURE,
- State.TERMINATED_SECURITY_ERROR
- ));
- transitionBuilder.put(State.SESSION_INITIALIZED_PRE_APPROVED, ImmutableList.of(
- State.SESSION_ACCEPTED,
- State.TERMINATED_SUCCESS,
- State.TERMINATED_DECLINED_OR_BUSY,
- State.TERMINATED_CONNECTIVITY_ERROR, //at this state used for IQ errors and IQ timeouts
- State.TERMINATED_CANCEL_OR_TIMEOUT,
- State.TERMINATED_APPLICATION_FAILURE,
- State.TERMINATED_SECURITY_ERROR
- ));
- transitionBuilder.put(State.SESSION_ACCEPTED, ImmutableList.of(
- State.TERMINATED_SUCCESS,
- State.TERMINATED_DECLINED_OR_BUSY,
- State.TERMINATED_CONNECTIVITY_ERROR,
- State.TERMINATED_CANCEL_OR_TIMEOUT,
- State.TERMINATED_APPLICATION_FAILURE,
- State.TERMINATED_SECURITY_ERROR
- ));
+ ImmutableList.of(
+ State.TERMINATED_SUCCESS,
+ State.TERMINATED_DECLINED_OR_BUSY,
+ State.TERMINATED_CONNECTIVITY_ERROR,
+ State.TERMINATED_CANCEL_OR_TIMEOUT,
+ State.TERMINATED_APPLICATION_FAILURE,
+ State.TERMINATED_SECURITY_ERROR));
VALID_TRANSITIONS = transitionBuilder.build();
}
private final WebRTCWrapper webRTCWrapper = new WebRTCWrapper(this);
- private final Queue<Map.Entry<String, RtpContentMap.DescriptionTransport>> pendingIceCandidates = new LinkedList<>();
+ private final Queue<Map.Entry<String, RtpContentMap.DescriptionTransport>>
+ pendingIceCandidates = new LinkedList<>();
private final OmemoVerification omemoVerification = new OmemoVerification();
private final Message message;
private State state = State.NULL;
- private StateTransitionException stateTransitionException;
private Set<Media> proposedMedia;
private RtpContentMap initiatorRtpContentMap;
private RtpContentMap responderRtpContentMap;
@@ -157,18 +169,16 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
JingleRtpConnection(JingleConnectionManager jingleConnectionManager, Id id, Jid initiator) {
super(jingleConnectionManager, id, initiator);
- final Conversation conversation = jingleConnectionManager.getXmppConnectionService().findOrCreateConversation(
- id.account,
- id.with.asBareJid(),
- false,
- false
- );
- this.message = new Message(
- conversation,
- isInitiator() ? Message.STATUS_SEND : Message.STATUS_RECEIVED,
- Message.TYPE_RTP_SESSION,
- id.sessionId
- );
+ final Conversation conversation =
+ jingleConnectionManager
+ .getXmppConnectionService()
+ .findOrCreateConversation(id.account, id.with.asBareJid(), false, false);
+ this.message =
+ new Message(
+ conversation,
+ isInitiator() ? Message.STATUS_SEND : Message.STATUS_RECEIVED,
+ Message.TYPE_RTP_SESSION,
+ id.sessionId);
}
private static State reasonToState(Reason reason) {
@@ -209,7 +219,11 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
break;
default:
respondOk(jinglePacket);
- Log.d(Config.LOGTAG, String.format("%s: received unhandled jingle action %s", id.account.getJid().asBareJid(), jinglePacket.getAction()));
+ Log.d(
+ Config.LOGTAG,
+ String.format(
+ "%s: received unhandled jingle action %s",
+ id.account.getJid().asBareJid(), jinglePacket.getAction()));
break;
}
}
@@ -223,8 +237,12 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
if (!isInitiator() && isInState(State.PROPOSED, State.SESSION_INITIALIZED)) {
xmppConnectionService.getNotificationService().cancelIncomingCallNotification();
}
- if (isInState(State.SESSION_INITIALIZED, State.SESSION_INITIALIZED_PRE_APPROVED, State.SESSION_ACCEPTED)) {
- //we might have already changed resources (full jid) at this point; so this might not even reach the other party
+ if (isInState(
+ State.SESSION_INITIALIZED,
+ State.SESSION_INITIALIZED_PRE_APPROVED,
+ State.SESSION_ACCEPTED)) {
+ // we might have already changed resources (full jid) at this point; so this might not
+ // even reach the other party
sendSessionTerminate(Reason.CONNECTIVITY_ERROR);
} else {
transitionOrThrow(State.TERMINATED_CONNECTIVITY_ERROR);
@@ -240,9 +258,21 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
respondOk(jinglePacket);
final JinglePacket.ReasonWrapper wrapper = jinglePacket.getReason();
final State previous = this.state;
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received session terminate reason=" + wrapper.reason + "(" + Strings.nullToEmpty(wrapper.text) + ") while in state " + previous);
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid()
+ + ": received session terminate reason="
+ + wrapper.reason
+ + "("
+ + Strings.nullToEmpty(wrapper.text)
+ + ") while in state "
+ + previous);
if (TERMINATED.contains(previous)) {
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": ignoring session terminate because already in " + previous);
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid()
+ + ": ignoring session terminate because already in "
+ + previous);
return;
}
webRTCWrapper.close();
@@ -256,13 +286,23 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
}
private void receiveTransportInfo(final JinglePacket jinglePacket) {
- //Due to the asynchronicity of processing session-init we might move from NULL|PROCEED to INITIALIZED only after transport-info has been received
- if (isInState(State.NULL, State.PROCEED, State.SESSION_INITIALIZED, State.SESSION_INITIALIZED_PRE_APPROVED, State.SESSION_ACCEPTED)) {
+ // Due to the asynchronicity of processing session-init we might move from NULL|PROCEED to
+ // INITIALIZED only after transport-info has been received
+ if (isInState(
+ State.NULL,
+ State.PROCEED,
+ State.SESSION_INITIALIZED,
+ State.SESSION_INITIALIZED_PRE_APPROVED,
+ State.SESSION_ACCEPTED)) {
final RtpContentMap contentMap;
try {
contentMap = RtpContentMap.of(jinglePacket);
} catch (final IllegalArgumentException | NullPointerException e) {
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": improperly formatted contents; ignoring", e);
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid()
+ + ": improperly formatted contents; ignoring",
+ e);
respondOk(jinglePacket);
return;
}
@@ -270,18 +310,27 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
} else {
if (isTerminated()) {
respondOk(jinglePacket);
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": ignoring out-of-order transport info; we where already terminated");
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid()
+ + ": ignoring out-of-order transport info; we where already terminated");
} else {
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received transport info while in state=" + this.state);
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid()
+ + ": received transport info while in state="
+ + this.state);
terminateWithOutOfOrder(jinglePacket);
}
}
}
- private void receiveTransportInfo(final JinglePacket jinglePacket, final RtpContentMap contentMap) {
- final Set<Map.Entry<String, RtpContentMap.DescriptionTransport>> candidates = contentMap.contents.entrySet();
+ private void receiveTransportInfo(
+ final JinglePacket jinglePacket, final RtpContentMap contentMap) {
+ final Set<Map.Entry<String, RtpContentMap.DescriptionTransport>> candidates =
+ contentMap.contents.entrySet();
if (this.state == State.SESSION_ACCEPTED) {
- //zero candidates + modified credentials are an ICE restart offer
+ // zero candidates + modified credentials are an ICE restart offer
if (checkForIceRestart(jinglePacket, contentMap)) {
return;
}
@@ -289,7 +338,10 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
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");
+ 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 {
respondOk(jinglePacket);
@@ -297,21 +349,23 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
}
}
- private boolean checkForIceRestart(final JinglePacket jinglePacket, final RtpContentMap rtpContentMap) {
+ private boolean checkForIceRestart(
+ final JinglePacket jinglePacket, final RtpContentMap rtpContentMap) {
final RtpContentMap existing = getRemoteContentMap();
- final IceUdpTransportInfo.Credentials existingCredentials;
+ final Set<IceUdpTransportInfo.Credentials> existingCredentials;
final IceUdpTransportInfo.Credentials newCredentials;
try {
existingCredentials = existing.getCredentials();
- newCredentials = rtpContentMap.getCredentials();
+ newCredentials = rtpContentMap.getDistinctCredentials();
} catch (final IllegalStateException e) {
Log.d(Config.LOGTAG, "unable to gather credentials for comparison", e);
return false;
}
- if (existingCredentials.equals(newCredentials)) {
+ if (existingCredentials.contains(newCredentials)) {
return false;
}
- //TODO an alternative approach is to check if we already got an iq result to our ICE-restart
+ // TODO an alternative approach is to check if we already got an iq result to our
+ // ICE-restart
// and if that's the case we are seeing an answer.
// This might be more spec compliant but also more error prone potentially
final boolean isOffer = rtpContentMap.emptyCandidates();
@@ -319,10 +373,17 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
try {
if (isOffer) {
Log.d(Config.LOGTAG, "received offer to restart ICE " + newCredentials);
- restartContentMap = existing.modifiedCredentials(newCredentials, IceUdpTransportInfo.Setup.ACTPASS);
+ restartContentMap =
+ existing.modifiedCredentials(
+ newCredentials, IceUdpTransportInfo.Setup.ACTPASS);
} else {
final IceUdpTransportInfo.Setup setup = getPeerDtlsSetup();
- Log.d(Config.LOGTAG, "received confirmation of ICE restart" + newCredentials + " peer_setup=" + setup);
+ Log.d(
+ Config.LOGTAG,
+ "received confirmation of ICE restart"
+ + newCredentials
+ + " peer_setup="
+ + setup);
// DTLS setup attribute needs to be rewritten to reflect current peer state
// https://groups.google.com/g/discuss-webrtc/c/DfpIMwvUfeM
restartContentMap = existing.modifiedCredentials(newCredentials, setup);
@@ -338,7 +399,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
respondOk(jinglePacket);
final Throwable rootCause = Throwables.getRootCause(exception);
if (rootCause instanceof WebRTCWrapper.PeerConnectionNotInitialized) {
- //If this happens a termination is already in progress
+ // If this happens a termination is already in progress
Log.d(Config.LOGTAG, "ignoring PeerConnectionNotInitialized on ICE restart");
return true;
}
@@ -364,13 +425,22 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
this.peerDtlsSetup = setup;
}
- private boolean applyIceRestart(final JinglePacket jinglePacket, final RtpContentMap restartContentMap, final boolean isOffer) throws ExecutionException, InterruptedException {
+ private boolean applyIceRestart(
+ final JinglePacket jinglePacket,
+ final RtpContentMap restartContentMap,
+ final boolean isOffer)
+ throws ExecutionException, InterruptedException {
final SessionDescription sessionDescription = SessionDescription.of(restartContentMap);
- final org.webrtc.SessionDescription.Type type = isOffer ? org.webrtc.SessionDescription.Type.OFFER : org.webrtc.SessionDescription.Type.ANSWER;
- org.webrtc.SessionDescription sdp = new org.webrtc.SessionDescription(type, sessionDescription.toString());
+ final org.webrtc.SessionDescription.Type type =
+ isOffer
+ ? org.webrtc.SessionDescription.Type.OFFER
+ : org.webrtc.SessionDescription.Type.ANSWER;
+ org.webrtc.SessionDescription sdp =
+ new org.webrtc.SessionDescription(type, sessionDescription.toString());
if (isOffer && webRTCWrapper.getSignalingState() != PeerConnection.SignalingState.STABLE) {
if (isInitiator()) {
- //We ignore the offer and respond with tie-break. This will clause the responder not to apply the content map
+ // We ignore the offer and respond with tie-break. This will clause the responder
+ // not to apply the content map
return false;
}
}
@@ -380,7 +450,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
webRTCWrapper.setIsReadyToReceiveIceCandidates(false);
final SessionDescription localSessionDescription = setLocalSessionDescription();
setLocalContentMap(RtpContentMap.of(localSessionDescription));
- //We need to respond OK before sending any candidates
+ // We need to respond OK before sending any candidates
respondOk(jinglePacket);
webRTCWrapper.setIsReadyToReceiveIceCandidates(true);
} else {
@@ -389,32 +459,40 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
return true;
}
- private void processCandidates(final Set<Map.Entry<String, RtpContentMap.DescriptionTransport>> contents) {
+ private void processCandidates(
+ final Set<Map.Entry<String, RtpContentMap.DescriptionTransport>> contents) {
for (final Map.Entry<String, RtpContentMap.DescriptionTransport> content : contents) {
processCandidate(content);
}
}
- private void processCandidate(final Map.Entry<String, RtpContentMap.DescriptionTransport> content) {
+ private void processCandidate(
+ final Map.Entry<String, RtpContentMap.DescriptionTransport> content) {
final RtpContentMap rtpContentMap = getRemoteContentMap();
final List<String> indices = toIdentificationTags(rtpContentMap);
- final String sdpMid = content.getKey(); //aka content name
+ final String sdpMid = content.getKey(); // aka content name
final IceUdpTransportInfo transport = content.getValue().transport;
final IceUdpTransportInfo.Credentials credentials = transport.getCredentials();
- //TODO check that credentials remained the same
+ // TODO check that credentials remained the same
for (final IceUdpTransportInfo.Candidate candidate : transport.getCandidates()) {
final String sdp;
try {
sdp = candidate.toSdpAttribute(credentials.ufrag);
} catch (final IllegalArgumentException e) {
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": ignoring invalid ICE candidate " + e.getMessage());
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid()
+ + ": ignoring invalid ICE candidate "
+ + e.getMessage());
continue;
}
final int mLineIndex = indices.indexOf(sdpMid);
if (mLineIndex < 0) {
- Log.w(Config.LOGTAG, "mLineIndex not found for " + sdpMid + ". available indices " + indices);
+ Log.w(
+ Config.LOGTAG,
+ "mLineIndex not found for " + sdpMid + ". available indices " + indices);
}
final IceCandidate iceCandidate = new IceCandidate(sdpMid, mLineIndex, sdp);
Log.d(Config.LOGTAG, "received candidate: " + iceCandidate);
@@ -428,14 +506,21 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
private List<String> toIdentificationTags(final RtpContentMap rtpContentMap) {
final Group originalGroup = rtpContentMap.group;
- final List<String> identificationTags = originalGroup == null ? rtpContentMap.getNames() : originalGroup.getIdentificationTags();
+ final List<String> identificationTags =
+ originalGroup == null
+ ? rtpContentMap.getNames()
+ : originalGroup.getIdentificationTags();
if (identificationTags.size() == 0) {
- Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": no identification tags found in initial offer. we won't be able to calculate mLineIndices");
+ Log.w(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid()
+ + ": no identification tags found in initial offer. we won't be able to calculate mLineIndices");
}
return identificationTags;
}
- private ListenableFuture<RtpContentMap> receiveRtpContentMap(final JinglePacket jinglePacket, final boolean expectVerification) {
+ private ListenableFuture<RtpContentMap> receiveRtpContentMap(
+ final JinglePacket jinglePacket, final boolean expectVerification) {
final RtpContentMap receivedContentMap;
try {
receivedContentMap = RtpContentMap.of(jinglePacket);
@@ -443,17 +528,26 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
return Futures.immediateFailedFuture(e);
}
if (receivedContentMap instanceof OmemoVerifiedRtpContentMap) {
- final ListenableFuture<AxolotlService.OmemoVerifiedPayload<RtpContentMap>> future = id.account.getAxolotlService().decrypt((OmemoVerifiedRtpContentMap) receivedContentMap, id.with);
- return Futures.transform(future, omemoVerifiedPayload -> {
- //TODO test if an exception here triggers a correct abort
- omemoVerification.setOrEnsureEqual(omemoVerifiedPayload);
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received verifiable DTLS fingerprint via " + omemoVerification);
- return omemoVerifiedPayload.getPayload();
- }, MoreExecutors.directExecutor());
+ final ListenableFuture<AxolotlService.OmemoVerifiedPayload<RtpContentMap>> future =
+ id.account
+ .getAxolotlService()
+ .decrypt((OmemoVerifiedRtpContentMap) receivedContentMap, id.with);
+ return Futures.transform(
+ future,
+ omemoVerifiedPayload -> {
+ // TODO test if an exception here triggers a correct abort
+ omemoVerification.setOrEnsureEqual(omemoVerifiedPayload);
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid()
+ + ": received verifiable DTLS fingerprint via "
+ + omemoVerification);
+ return omemoVerifiedPayload.getPayload();
+ },
+ MoreExecutors.directExecutor());
} else if (Config.REQUIRE_RTP_VERIFICATION || expectVerification) {
return Futures.immediateFailedFuture(
- new SecurityException("DTLS fingerprint was unexpectedly not verifiable")
- );
+ new SecurityException("DTLS fingerprint was unexpectedly not verifiable"));
} else {
return Futures.immediateFuture(receivedContentMap);
}
@@ -461,13 +555,17 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
private void receiveSessionInitiate(final JinglePacket jinglePacket) {
if (isInitiator()) {
- Log.d(Config.LOGTAG, String.format("%s: received session-initiate even though we were initiating", id.account.getJid().asBareJid()));
+ Log.d(
+ Config.LOGTAG,
+ String.format(
+ "%s: received session-initiate even though we were initiating",
+ id.account.getJid().asBareJid()));
if (isTerminated()) {
- Log.d(Config.LOGTAG, String.format(
- "%s: got a reason to terminate with out-of-order. but already in state %s",
- id.account.getJid().asBareJid(),
- getState()
- ));
+ Log.d(
+ Config.LOGTAG,
+ String.format(
+ "%s: got a reason to terminate with out-of-order. but already in state %s",
+ id.account.getJid().asBareJid(), getState()));
respondWithOutOfOrder(jinglePacket);
} else {
terminateWithOutOfOrder(jinglePacket);
@@ -475,43 +573,51 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
return;
}
final ListenableFuture<RtpContentMap> future = receiveRtpContentMap(jinglePacket, false);
- Futures.addCallback(future, new FutureCallback<RtpContentMap>() {
- @Override
- public void onSuccess(@Nullable RtpContentMap rtpContentMap) {
- receiveSessionInitiate(jinglePacket, rtpContentMap);
- }
+ Futures.addCallback(
+ future,
+ new FutureCallback<RtpContentMap>() {
+ @Override
+ public void onSuccess(@Nullable RtpContentMap rtpContentMap) {
+ receiveSessionInitiate(jinglePacket, rtpContentMap);
+ }
- @Override
- public void onFailure(@NonNull final Throwable throwable) {
- respondOk(jinglePacket);
- sendSessionTerminate(Reason.ofThrowable(throwable), throwable.getMessage());
- }
- }, MoreExecutors.directExecutor());
+ @Override
+ public void onFailure(@NonNull final Throwable throwable) {
+ respondOk(jinglePacket);
+ sendSessionTerminate(Reason.ofThrowable(throwable), throwable.getMessage());
+ }
+ },
+ MoreExecutors.directExecutor());
}
- private void receiveSessionInitiate(final JinglePacket jinglePacket, final RtpContentMap contentMap) {
+ private void receiveSessionInitiate(
+ final JinglePacket jinglePacket, final RtpContentMap contentMap) {
try {
contentMap.requireContentDescriptions();
contentMap.requireDTLSFingerprint(true);
} catch (final RuntimeException e) {
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": improperly formatted contents", Throwables.getRootCause(e));
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid() + ": improperly formatted contents",
+ Throwables.getRootCause(e));
respondOk(jinglePacket);
sendSessionTerminate(Reason.of(e), e.getMessage());
return;
}
- Log.d(Config.LOGTAG, "processing session-init with " + contentMap.contents.size() + " contents");
+ Log.d(
+ Config.LOGTAG,
+ "processing session-init with " + contentMap.contents.size() + " contents");
final State target;
if (this.state == State.PROCEED) {
Preconditions.checkState(
proposedMedia != null && proposedMedia.size() > 0,
- "proposed media must be set when processing pre-approved session-initiate"
- );
+ "proposed media must be set when processing pre-approved session-initiate");
if (!this.proposedMedia.equals(contentMap.getMedia())) {
- sendSessionTerminate(Reason.SECURITY_ERROR, String.format(
- "Your session proposal (Jingle Message Initiation) included media %s but your session-initiate was %s",
- this.proposedMedia,
- contentMap.getMedia()
- ));
+ sendSessionTerminate(
+ Reason.SECURITY_ERROR,
+ String.format(
+ "Your session proposal (Jingle Message Initiation) included media %s but your session-initiate was %s",
+ this.proposedMedia, contentMap.getMedia()));
return;
}
target = State.SESSION_INITIALIZED_PRE_APPROVED;
@@ -522,67 +628,100 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
respondOk(jinglePacket);
pendingIceCandidates.addAll(contentMap.contents.entrySet());
if (target == State.SESSION_INITIALIZED_PRE_APPROVED) {
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": automatically accepting session-initiate");
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid()
+ + ": automatically accepting session-initiate");
sendSessionAccept();
} else {
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received not pre-approved session-initiate. start ringing");
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid()
+ + ": received not pre-approved session-initiate. start ringing");
startRinging();
}
} else {
- Log.d(Config.LOGTAG, String.format("%s: received session-initiate while in state %s", id.account.getJid().asBareJid(), state));
+ Log.d(
+ Config.LOGTAG,
+ String.format(
+ "%s: received session-initiate while in state %s",
+ id.account.getJid().asBareJid(), state));
terminateWithOutOfOrder(jinglePacket);
}
}
private void receiveSessionAccept(final JinglePacket jinglePacket) {
if (!isInitiator()) {
- Log.d(Config.LOGTAG, String.format("%s: received session-accept even though we were responding", id.account.getJid().asBareJid()));
+ Log.d(
+ Config.LOGTAG,
+ String.format(
+ "%s: received session-accept even though we were responding",
+ id.account.getJid().asBareJid()));
terminateWithOutOfOrder(jinglePacket);
return;
}
- final ListenableFuture<RtpContentMap> future = receiveRtpContentMap(jinglePacket, this.omemoVerification.hasFingerprint());
- Futures.addCallback(future, new FutureCallback<RtpContentMap>() {
- @Override
- public void onSuccess(@Nullable RtpContentMap rtpContentMap) {
- receiveSessionAccept(jinglePacket, rtpContentMap);
- }
+ final ListenableFuture<RtpContentMap> future =
+ receiveRtpContentMap(jinglePacket, this.omemoVerification.hasFingerprint());
+ Futures.addCallback(
+ future,
+ new FutureCallback<RtpContentMap>() {
+ @Override
+ public void onSuccess(@Nullable RtpContentMap rtpContentMap) {
+ receiveSessionAccept(jinglePacket, rtpContentMap);
+ }
- @Override
- public void onFailure(@NonNull final Throwable throwable) {
- respondOk(jinglePacket);
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": improperly formatted contents in session-accept", throwable);
- webRTCWrapper.close();
- sendSessionTerminate(Reason.ofThrowable(throwable), throwable.getMessage());
- }
- }, MoreExecutors.directExecutor());
+ @Override
+ public void onFailure(@NonNull final Throwable throwable) {
+ respondOk(jinglePacket);
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid()
+ + ": improperly formatted contents in session-accept",
+ throwable);
+ webRTCWrapper.close();
+ sendSessionTerminate(Reason.ofThrowable(throwable), throwable.getMessage());
+ }
+ },
+ MoreExecutors.directExecutor());
}
- private void receiveSessionAccept(final JinglePacket jinglePacket, final RtpContentMap contentMap) {
+ private void receiveSessionAccept(
+ final JinglePacket jinglePacket, final RtpContentMap contentMap) {
try {
contentMap.requireContentDescriptions();
contentMap.requireDTLSFingerprint();
} catch (final RuntimeException e) {
respondOk(jinglePacket);
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": improperly formatted contents in session-accept", e);
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid()
+ + ": improperly formatted contents in session-accept",
+ e);
webRTCWrapper.close();
sendSessionTerminate(Reason.of(e), e.getMessage());
return;
}
final Set<Media> initiatorMedia = this.initiatorRtpContentMap.getMedia();
if (!initiatorMedia.equals(contentMap.getMedia())) {
- sendSessionTerminate(Reason.SECURITY_ERROR, String.format(
- "Your session-included included media %s but our session-initiate was %s",
- this.proposedMedia,
- contentMap.getMedia()
- ));
+ sendSessionTerminate(
+ Reason.SECURITY_ERROR,
+ String.format(
+ "Your session-included included media %s but our session-initiate was %s",
+ this.proposedMedia, contentMap.getMedia()));
return;
}
- Log.d(Config.LOGTAG, "processing session-accept with " + contentMap.contents.size() + " contents");
+ Log.d(
+ Config.LOGTAG,
+ "processing session-accept with " + contentMap.contents.size() + " contents");
if (transition(State.SESSION_ACCEPTED)) {
respondOk(jinglePacket);
receiveSessionAccept(contentMap);
} else {
- Log.d(Config.LOGTAG, String.format("%s: received session-accept while in state %s", id.account.getJid().asBareJid(), state));
+ Log.d(
+ Config.LOGTAG,
+ String.format(
+ "%s: received session-accept while in state %s",
+ id.account.getJid().asBareJid(), state));
respondOk(jinglePacket);
}
}
@@ -594,21 +733,29 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
try {
sessionDescription = SessionDescription.of(contentMap);
} catch (final IllegalArgumentException | NullPointerException e) {
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable convert offer from session-accept to SDP", e);
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid()
+ + ": unable convert offer from session-accept to SDP",
+ e);
webRTCWrapper.close();
sendSessionTerminate(Reason.FAILED_APPLICATION, e.getMessage());
return;
}
- final org.webrtc.SessionDescription answer = new org.webrtc.SessionDescription(
- org.webrtc.SessionDescription.Type.ANSWER,
- sessionDescription.toString()
- );
+ final org.webrtc.SessionDescription answer =
+ new org.webrtc.SessionDescription(
+ org.webrtc.SessionDescription.Type.ANSWER, sessionDescription.toString());
try {
this.webRTCWrapper.setRemoteDescription(answer).get();
} catch (final Exception e) {
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to set remote description after receiving session-accept", Throwables.getRootCause(e));
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid()
+ + ": unable to set remote description after receiving session-accept",
+ Throwables.getRootCause(e));
webRTCWrapper.close();
- sendSessionTerminate(Reason.FAILED_APPLICATION, Throwables.getRootCause(e).getMessage());
+ sendSessionTerminate(
+ Reason.FAILED_APPLICATION, Throwables.getRootCause(e).getMessage());
return;
}
processCandidates(contentMap.contents.entrySet());
@@ -623,7 +770,11 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
try {
offer = SessionDescription.of(rtpContentMap);
} catch (final IllegalArgumentException | NullPointerException e) {
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable convert offer from session-initiate to SDP", e);
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid()
+ + ": unable convert offer from session-initiate to SDP",
+ e);
webRTCWrapper.close();
sendSessionTerminate(Reason.FAILED_APPLICATION, e.getMessage());
return;
@@ -635,9 +786,15 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
discoverIceServers(iceServers -> sendSessionAccept(media, offer, iceServers));
}
- private synchronized void sendSessionAccept(final Set<Media> media, final SessionDescription offer, final List<PeerConnection.IceServer> iceServers) {
+ private synchronized void sendSessionAccept(
+ final Set<Media> media,
+ final SessionDescription offer,
+ final List<PeerConnection.IceServer> iceServers) {
if (isTerminated()) {
- Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": ICE servers got discovered when session was already terminated. nothing to do.");
+ Log.w(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid()
+ + ": ICE servers got discovered when session was already terminated. nothing to do.");
return;
}
try {
@@ -648,14 +805,14 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
sendSessionTerminate(Reason.FAILED_APPLICATION);
return;
}
- final org.webrtc.SessionDescription sdp = new org.webrtc.SessionDescription(
- org.webrtc.SessionDescription.Type.OFFER,
- offer.toString()
- );
+ final org.webrtc.SessionDescription sdp =
+ new org.webrtc.SessionDescription(
+ org.webrtc.SessionDescription.Type.OFFER, offer.toString());
try {
this.webRTCWrapper.setRemoteDescription(sdp).get();
addIceCandidatesFromBlackLog();
- org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.setLocalDescription().get();
+ org.webrtc.SessionDescription webRTCSessionDescription =
+ this.webRTCWrapper.setLocalDescription().get();
prepareSessionAccept(webRTCSessionDescription);
} catch (final Exception e) {
failureToAcceptSession(e);
@@ -676,18 +833,24 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
Map.Entry<String, RtpContentMap.DescriptionTransport> foo;
while ((foo = this.pendingIceCandidates.poll()) != null) {
processCandidate(foo);
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": added candidate from back log");
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid() + ": added candidate from back log");
}
}
- private void prepareSessionAccept(final org.webrtc.SessionDescription webRTCSessionDescription) {
- final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description);
+ private void prepareSessionAccept(
+ final org.webrtc.SessionDescription webRTCSessionDescription) {
+ final SessionDescription sessionDescription =
+ SessionDescription.parse(webRTCSessionDescription.description);
final RtpContentMap respondingRtpContentMap = RtpContentMap.of(sessionDescription);
this.responderRtpContentMap = respondingRtpContentMap;
storePeerDtlsSetup(respondingRtpContentMap.getDtlsSetup().flip());
webRTCWrapper.setIsReadyToReceiveIceCandidates(true);
- final ListenableFuture<RtpContentMap> outgoingContentMapFuture = prepareOutgoingContentMap(respondingRtpContentMap);
- Futures.addCallback(outgoingContentMapFuture,
+ final ListenableFuture<RtpContentMap> outgoingContentMapFuture =
+ prepareOutgoingContentMap(respondingRtpContentMap);
+ Futures.addCallback(
+ outgoingContentMapFuture,
new FutureCallback<RtpContentMap>() {
@Override
public void onSuccess(final RtpContentMap outgoingContentMap) {
@@ -699,35 +862,56 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
failureToAcceptSession(throwable);
}
},
- MoreExecutors.directExecutor()
- );
+ MoreExecutors.directExecutor());
}
private void sendSessionAccept(final RtpContentMap rtpContentMap) {
if (isTerminated()) {
- Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": preparing session accept was too slow. already terminated. nothing to do.");
+ Log.w(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid()
+ + ": preparing session accept was too slow. already terminated. nothing to do.");
return;
}
transitionOrThrow(State.SESSION_ACCEPTED);
- final JinglePacket sessionAccept = rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_ACCEPT, id.sessionId);
+ final JinglePacket sessionAccept =
+ rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_ACCEPT, id.sessionId);
send(sessionAccept);
}
- private ListenableFuture<RtpContentMap> prepareOutgoingContentMap(final RtpContentMap rtpContentMap) {
+ private ListenableFuture<RtpContentMap> prepareOutgoingContentMap(
+ final RtpContentMap rtpContentMap) {
if (this.omemoVerification.hasDeviceId()) {
- ListenableFuture<AxolotlService.OmemoVerifiedPayload<OmemoVerifiedRtpContentMap>> verifiedPayloadFuture = id.account.getAxolotlService()
- .encrypt(rtpContentMap, id.with, omemoVerification.getDeviceId());
- return Futures.transform(verifiedPayloadFuture, verifiedPayload -> {
- omemoVerification.setOrEnsureEqual(verifiedPayload);
- return verifiedPayload.getPayload();
- }, MoreExecutors.directExecutor());
+ ListenableFuture<AxolotlService.OmemoVerifiedPayload<OmemoVerifiedRtpContentMap>>
+ verifiedPayloadFuture =
+ id.account
+ .getAxolotlService()
+ .encrypt(
+ rtpContentMap,
+ id.with,
+ omemoVerification.getDeviceId());
+ return Futures.transform(
+ verifiedPayloadFuture,
+ verifiedPayload -> {
+ omemoVerification.setOrEnsureEqual(verifiedPayload);
+ return verifiedPayload.getPayload();
+ },
+ MoreExecutors.directExecutor());
} else {
return Futures.immediateFuture(rtpContentMap);
}
}
- synchronized void deliveryMessage(final Jid from, final Element message, final String serverMessageId, final long timestamp) {
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": delivered message to JingleRtpConnection " + message);
+ synchronized void deliveryMessage(
+ final Jid from,
+ final Element message,
+ final String serverMessageId,
+ final long timestamp) {
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid()
+ + ": delivered message to JingleRtpConnection "
+ + message);
switch (message.getName()) {
case "propose":
receivePropose(from, Propose.upgrade(message), serverMessageId, timestamp);
@@ -750,47 +934,73 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web
}
void deliverFailedProceed() {
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": receive message error for proceed message");
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid() + ": receive message error for proceed message");
if (transition(State.TERMINATED_CONNECTIVITY_ERROR)) {
webRTCWrapper.close();
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": transitioned into connectivity error");
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid() + ": transitioned into connectivity error");
this.finish();
}
}
private void receiveAccept(final Jid from, final String serverMsgId, final long timestamp) {
- final boolean originatedFromMyself = from.asBareJid().equals(id.account.getJid().asBareJid());
+ final boolean originatedFromMyself =
+ from.asBareJid().equals(id.account.getJid().asBareJid());
if (originatedFromMyself) {
if (transition(State.ACCEPTED)) {
if (serverMsgId != null) {
this.message.setServerMsgId(serverMsgId);
}
this.message.setTime(timestamp);
- this.message.setCarbon(true); //indicate that call was accepted on other device
+ this.message.setCarbon(true); // indicate that call was accepted on other device
this.writeLogMessageSuccess(0);
- this.xmppConnectionService.getNotificationService().cancelIncomingCallNotification();
+ this.xmppConnectionService
+ .getNotificationService()
+ .cancelIncomingCallNotification();
this.finish();
} else {
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to transition to accept because already in state=" + this.state);
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid()
+ + ": unable to transition to accept because already in state="
+ + this.state);
}
} else {
- Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": ignoring 'accept' from " + from);
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid().asBareJid() + ": ignoring 'accept' from " + from);
}
}
private void receiveReject(final Jid from, final String serverMsgId, final long timestamp) {
- final boolean originatedFromMyself = from.asBareJid().equals(id.account.getJid().asBareJid());
- //reject from another one of my clients
+ final boolean originatedFromMyself =
+ from.asBareJid().equals(id.account.getJid().asBareJid());
+ // reject from another one of my clients
if (originatedFromMyself) {
receiveRejectFromMyself(serverMsgId, timestamp);
} else if (isInitiator()) {
if (from.equals(id.with)) {
receiveRejectFromResponder();
} else {
- Log.d(Config.LOGTAG, id.account.getJid() + ": ignoring reject from " + from + " for session with " + id.with);
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid()
+ + ": ignoring reject from "
+ + from
+ + " for session with "
+ + id.with);
}
} else {
- Log.d(Config.LOGTAG, id.account.getJid() + ": ignoring reject from " + from + " for session with " + id.with);
+ Log.d(
+ Config.LOGTAG,
+ id.account.getJid()
+ + ": ignoring reject from "
+ + from
+ + " for session with "
+ + id.with);
}
}
@@ -16,7 +16,6 @@ import org.checkerframework.checker.nullness.compatqual.NullableDecl;
import java.util.Collection;
import java.util.List;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
@@ -39,7 +38,8 @@ public class RtpContentMap {
}
public static RtpContentMap of(final JinglePacket jinglePacket) {
- final Map<String, DescriptionTransport> contents = DescriptionTransport.of(jinglePacket.getJingleContents());
+ final Map<String, DescriptionTransport> contents =
+ DescriptionTransport.of(jinglePacket.getJingleContents());
if (isOmemoVerified(contents)) {
return new OmemoVerifiedRtpContentMap(jinglePacket.getGroup(), contents);
} else {
@@ -62,22 +62,30 @@ public class RtpContentMap {
}
public static RtpContentMap of(final SessionDescription sessionDescription) {
- final ImmutableMap.Builder<String, DescriptionTransport> contentMapBuilder = new ImmutableMap.Builder<>();
+ final ImmutableMap.Builder<String, DescriptionTransport> contentMapBuilder =
+ new ImmutableMap.Builder<>();
for (SessionDescription.Media media : sessionDescription.media) {
final String id = Iterables.getFirst(media.attributes.get("mid"), null);
Preconditions.checkNotNull(id, "media has no mid");
contentMapBuilder.put(id, DescriptionTransport.of(sessionDescription, media));
}
- final String groupAttribute = Iterables.getFirst(sessionDescription.attributes.get("group"), null);
+ final String groupAttribute =
+ Iterables.getFirst(sessionDescription.attributes.get("group"), null);
final Group group = groupAttribute == null ? null : Group.ofSdpString(groupAttribute);
return new RtpContentMap(group, contentMapBuilder.build());
}
public Set<Media> getMedia() {
- return Sets.newHashSet(Collections2.transform(contents.values(), input -> {
- final RtpDescription rtpDescription = input == null ? null : input.description;
- return rtpDescription == null ? Media.UNKNOWN : input.description.getMedia();
- }));
+ return Sets.newHashSet(
+ Collections2.transform(
+ contents.values(),
+ input -> {
+ final RtpDescription rtpDescription =
+ input == null ? null : input.description;
+ return rtpDescription == null
+ ? Media.UNKNOWN
+ : input.description.getMedia();
+ }));
}
public List<String> getNames() {
@@ -90,7 +98,8 @@ public class RtpContentMap {
}
for (Map.Entry<String, DescriptionTransport> entry : this.contents.entrySet()) {
if (entry.getValue().description == null) {
- throw new IllegalStateException(String.format("%s is lacking content description", entry.getKey()));
+ throw new IllegalStateException(
+ String.format("%s is lacking content description", entry.getKey()));
}
}
}
@@ -106,15 +115,24 @@ public class RtpContentMap {
for (Map.Entry<String, DescriptionTransport> entry : this.contents.entrySet()) {
final IceUdpTransportInfo transport = entry.getValue().transport;
final IceUdpTransportInfo.Fingerprint fingerprint = transport.getFingerprint();
- if (fingerprint == null || Strings.isNullOrEmpty(fingerprint.getContent()) || Strings.isNullOrEmpty(fingerprint.getHash())) {
- throw new SecurityException(String.format("Use of DTLS-SRTP (XEP-0320) is required for content %s", entry.getKey()));
+ if (fingerprint == null
+ || Strings.isNullOrEmpty(fingerprint.getContent())
+ || Strings.isNullOrEmpty(fingerprint.getHash())) {
+ throw new SecurityException(
+ String.format(
+ "Use of DTLS-SRTP (XEP-0320) is required for content %s",
+ entry.getKey()));
}
final IceUdpTransportInfo.Setup setup = fingerprint.getSetup();
if (setup == null) {
- throw new SecurityException(String.format("Use of DTLS-SRTP (XEP-0320) is required for content %s but missing setup attribute", entry.getKey()));
+ throw new SecurityException(
+ String.format(
+ "Use of DTLS-SRTP (XEP-0320) is required for content %s but missing setup attribute",
+ entry.getKey()));
}
if (requireActPass && setup != IceUdpTransportInfo.Setup.ACTPASS) {
- throw new SecurityException("Initiator needs to offer ACTPASS as setup for DTLS-SRTP (XEP-0320)");
+ throw new SecurityException(
+ "Initiator needs to offer ACTPASS as setup for DTLS-SRTP (XEP-0320)");
}
}
}
@@ -135,41 +153,66 @@ public class RtpContentMap {
return jinglePacket;
}
- RtpContentMap transportInfo(final String contentName, final IceUdpTransportInfo.Candidate candidate) {
+ RtpContentMap transportInfo(
+ final String contentName, final IceUdpTransportInfo.Candidate candidate) {
final RtpContentMap.DescriptionTransport descriptionTransport = contents.get(contentName);
- final IceUdpTransportInfo transportInfo = descriptionTransport == null ? null : descriptionTransport.transport;
+ final IceUdpTransportInfo transportInfo =
+ descriptionTransport == null ? null : descriptionTransport.transport;
if (transportInfo == null) {
- throw new IllegalArgumentException("Unable to find transport info for content name " + contentName);
+ throw new IllegalArgumentException(
+ "Unable to find transport info for content name " + contentName);
}
final IceUdpTransportInfo newTransportInfo = transportInfo.cloneWrapper();
newTransportInfo.addChild(candidate);
- return new RtpContentMap(null, ImmutableMap.of(contentName, new DescriptionTransport(null, newTransportInfo)));
+ return new RtpContentMap(
+ null,
+ ImmutableMap.of(contentName, new DescriptionTransport(null, newTransportInfo)));
}
RtpContentMap transportInfo() {
return new RtpContentMap(
null,
- Maps.transformValues(contents, dt -> new DescriptionTransport(null, dt.transport.cloneWrapper()))
- );
+ Maps.transformValues(
+ contents,
+ dt -> new DescriptionTransport(null, dt.transport.cloneWrapper())));
}
- public IceUdpTransportInfo.Credentials getCredentials() {
- final Set<IceUdpTransportInfo.Credentials> allCredentials = ImmutableSet.copyOf(Collections2.transform(
- contents.values(),
- dt -> dt.transport.getCredentials()
- ));
- final IceUdpTransportInfo.Credentials credentials = Iterables.getFirst(allCredentials, null);
+ public IceUdpTransportInfo.Credentials getDistinctCredentials() {
+ final Set<IceUdpTransportInfo.Credentials> allCredentials = getCredentials();
+ final IceUdpTransportInfo.Credentials credentials =
+ Iterables.getFirst(allCredentials, null);
if (allCredentials.size() == 1 && credentials != null) {
return credentials;
}
throw new IllegalStateException("Content map does not have distinct credentials");
}
+ public Set<IceUdpTransportInfo.Credentials> getCredentials() {
+ final Set<IceUdpTransportInfo.Credentials> credentials =
+ ImmutableSet.copyOf(
+ Collections2.transform(
+ contents.values(), dt -> dt.transport.getCredentials()));
+ if (credentials.isEmpty()) {
+ throw new IllegalStateException("Content map does not have any credentials");
+ }
+ return credentials;
+ }
+
+ public IceUdpTransportInfo.Credentials getCredentials(final String contentName) {
+ final DescriptionTransport descriptionTransport = this.contents.get(contentName);
+ if (descriptionTransport == null) {
+ throw new IllegalArgumentException(
+ String.format(
+ "Unable to find transport info for content name %s", contentName));
+ }
+ return descriptionTransport.transport.getCredentials();
+ }
+
public IceUdpTransportInfo.Setup getDtlsSetup() {
- final Set<IceUdpTransportInfo.Setup> setups = ImmutableSet.copyOf(Collections2.transform(
- contents.values(),
- dt -> dt.transport.getFingerprint().getSetup()
- ));
+ final Set<IceUdpTransportInfo.Setup> setups =
+ ImmutableSet.copyOf(
+ Collections2.transform(
+ contents.values(), dt -> dt.transport.getFingerprint().getSetup()));
final IceUdpTransportInfo.Setup setup = Iterables.getFirst(setups, null);
if (setups.size() == 1 && setup != null) {
return setup;
@@ -185,13 +228,18 @@ public class RtpContentMap {
return count == 0;
}
- public RtpContentMap modifiedCredentials(IceUdpTransportInfo.Credentials credentials, final IceUdpTransportInfo.Setup setup) {
- final ImmutableMap.Builder<String, DescriptionTransport> contentMapBuilder = new ImmutableMap.Builder<>();
+ public RtpContentMap modifiedCredentials(
+ IceUdpTransportInfo.Credentials credentials, final IceUdpTransportInfo.Setup setup) {
+ final ImmutableMap.Builder<String, DescriptionTransport> contentMapBuilder =
+ new ImmutableMap.Builder<>();
for (final Map.Entry<String, DescriptionTransport> content : contents.entrySet()) {
final RtpDescription rtpDescription = content.getValue().description;
IceUdpTransportInfo transportInfo = content.getValue().transport;
- final IceUdpTransportInfo modifiedTransportInfo = transportInfo.modifyCredentials(credentials, setup);
- contentMapBuilder.put(content.getKey(), new DescriptionTransport(rtpDescription, modifiedTransportInfo));
+ final IceUdpTransportInfo modifiedTransportInfo =
+ transportInfo.modifyCredentials(credentials, setup);
+ contentMapBuilder.put(
+ content.getKey(),
+ new DescriptionTransport(rtpDescription, modifiedTransportInfo));
}
return new RtpContentMap(this.group, contentMapBuilder.build());
}
@@ -200,7 +248,8 @@ public class RtpContentMap {
public final RtpDescription description;
public final IceUdpTransportInfo transport;
- public DescriptionTransport(final RtpDescription description, final IceUdpTransportInfo transport) {
+ public DescriptionTransport(
+ final RtpDescription description, final IceUdpTransportInfo transport) {
this.description = description;
this.transport = transport;
}
@@ -215,33 +264,38 @@ public class RtpContentMap {
} else if (description instanceof RtpDescription) {
rtpDescription = (RtpDescription) description;
} else {
- throw new UnsupportedApplicationException("Content does not contain rtp description");
+ throw new UnsupportedApplicationException(
+ "Content does not contain rtp description");
}
if (transportInfo instanceof IceUdpTransportInfo) {
iceUdpTransportInfo = (IceUdpTransportInfo) transportInfo;
} else {
- throw new UnsupportedTransportException("Content does not contain ICE-UDP transport");
+ throw new UnsupportedTransportException(
+ "Content does not contain ICE-UDP transport");
}
return new DescriptionTransport(
- rtpDescription,
- OmemoVerifiedIceUdpTransportInfo.upgrade(iceUdpTransportInfo)
- );
+ rtpDescription, OmemoVerifiedIceUdpTransportInfo.upgrade(iceUdpTransportInfo));
}
- public static DescriptionTransport of(final SessionDescription sessionDescription, final SessionDescription.Media media) {
+ public static DescriptionTransport of(
+ final SessionDescription sessionDescription, final SessionDescription.Media media) {
final RtpDescription rtpDescription = RtpDescription.of(sessionDescription, media);
- final IceUdpTransportInfo transportInfo = IceUdpTransportInfo.of(sessionDescription, media);
+ final IceUdpTransportInfo transportInfo =
+ IceUdpTransportInfo.of(sessionDescription, media);
return new DescriptionTransport(rtpDescription, transportInfo);
}
public static Map<String, DescriptionTransport> of(final Map<String, Content> contents) {
- return ImmutableMap.copyOf(Maps.transformValues(contents, new Function<Content, DescriptionTransport>() {
- @NullableDecl
- @Override
- public DescriptionTransport apply(@NullableDecl Content content) {
- return content == null ? null : of(content);
- }
- }));
+ return ImmutableMap.copyOf(
+ Maps.transformValues(
+ contents,
+ new Function<Content, DescriptionTransport>() {
+ @NullableDecl
+ @Override
+ public DescriptionTransport apply(@NullableDecl Content content) {
+ return content == null ? null : of(content);
+ }
+ }));
}
}
@@ -1,5 +1,7 @@
package eu.siacs.conversations.xmpp.jingle.stanzas;
+import androidx.annotation.NonNull;
+
import com.google.common.base.Joiner;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
@@ -123,6 +125,7 @@ public class IceUdpTransportInfo extends GenericTransportInfo {
}
@Override
+ @NonNull
public String toString() {
return MoreObjects.toStringHelper(this)
.add("ufrag", ufrag)
@@ -106,7 +106,7 @@
app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error"
app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint">
- <eu.siacs.conversations.ui.widget.EmojiWrapperEditText
+ <EditText
android:id="@+id/muc_edit_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -120,7 +120,7 @@
app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error"
app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint">
- <eu.siacs.conversations.ui.widget.EmojiWrapperEditText
+ <EditText
android:id="@+id/muc_edit_subject"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -55,23 +55,21 @@
android:layout_marginLeft="16dp"
android:layout_marginTop="0dp"
android:layout_marginRight="16dp"
- android:layout_marginBottom="16dp"
+ android:layout_marginBottom="8dp"
android:textAppearance="@style/TextAppearance.Conversations.Display2"
android:textColor="@color/white"
tools:text="Juliet Capulet" />
-
<TextView
android:id="@+id/with_jid"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_below="@id/with"
+ android:layout_below="@id/status"
android:layout_marginLeft="16dp"
- android:layout_marginTop="0dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="32dp"
- android:textAppearance="@style/TextAppearance.Conversations.Title"
+ android:textAppearance="@style/TextAppearance.Conversations.Body1"
android:textColor="@color/white"
- tools:text="juliet@capulet.lit" />
+ tools:text="jcapulet@example.com" />
</com.google.android.material.appbar.AppBarLayout>
@@ -256,5 +254,15 @@
app:tint="?attr/icon_tint" />
</RelativeLayout>
+ <TextView
+ android:id="@+id/using_account"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentEnd="true"
+ android:layout_margin="8dp"
+ android:text="@string/using_account"
+ android:textAppearance="@style/TextAppearance.Conversations.Caption"/>
+
</RelativeLayout>
</layout>
@@ -28,7 +28,7 @@
app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error"
app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint">
- <eu.siacs.conversations.ui.widget.EmojiWrapperEditText
+ <EditText
android:id="@+id/group_chat_name"
style="@style/Widget.Conversations.EditText"
android:layout_width="match_parent"
@@ -26,7 +26,7 @@
app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error"
app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint">
- <eu.siacs.conversations.ui.widget.EmojiWrapperEditText
+ <EditText
android:id="@+id/group_chat_name"
style="@style/Widget.Conversations.EditText"
android:layout_width="match_parent"
@@ -16,7 +16,7 @@
app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error"
app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint">
- <eu.siacs.conversations.ui.widget.EmojiWrapperEditText
+ <EditText
android:id="@+id/input_edit_text"
style="@style/Widget.Conversations.EditText"
android:layout_width="match_parent"
@@ -25,7 +25,7 @@
android:inputType="textPersonName">
<requestFocus/>
- </eu.siacs.conversations.ui.widget.EmojiWrapperEditText>
+ </EditText>
</com.google.android.material.textfield.TextInputLayout>
</LinearLayout>
@@ -965,5 +965,4 @@
<string name="backup_started_message">Създаването на резервно копие е стартирано. Ще получите известие, когато приключи.</string>
<string name="unable_to_enable_video">Видеото не може да бъде включено.</string>
<string name="plain_text_document">Обикновен текстов документ</string>
-
-</resources>
+ </resources>
@@ -968,5 +968,4 @@
<string name="backup_started_message">Sikkerhedskopieringen er startet. Du får en notifikation, når den er afsluttet.</string>
<string name="unable_to_enable_video">Kunne ikke aktivere video.</string>
<string name="plain_text_document">Ren tekstdokument</string>
-
-</resources>
+ </resources>
@@ -968,5 +968,4 @@
<string name="backup_started_message">Die Sicherung wurde gestartet. Du bekommst eine Benachrichtigung, sobald sie fertig ist.</string>
<string name="unable_to_enable_video">Video kann nicht aktiviert werden.</string>
<string name="plain_text_document">Textdokument</string>
-
-</resources>
+ </resources>
@@ -968,5 +968,4 @@
<string name="backup_started_message">La copia de seguridad ha empezado. Recibirás una notificación cuando se haya completado.</string>
<string name="unable_to_enable_video">No se ha podido habilitar el vídeo.</string>
<string name="plain_text_document">Documento de texto plano</string>
-
-</resources>
+ </resources>
@@ -914,5 +914,4 @@
<string name="backup_started_message">Varmuuskopion teko aloitettu. Saat ilmoituksen kun se on valmis.</string>
<string name="unable_to_enable_video">Videon käyttöönotto epäonnistui</string>
<string name="plain_text_document">Perustekstiasiakirja</string>
-
-</resources>
+ </resources>
@@ -479,7 +479,7 @@
<string name="pref_show_connection_options">Axustes ampliados de conexión</string>
<string name="pref_show_connection_options_summary">Mostar axustes de servidor e porto cando se configura unha conta</string>
<string name="hostname_example">xmpp.exemplo.com</string>
- <string name="action_add_account_with_certificate">Conéctate con certificado</string>
+ <string name="action_add_account_with_certificate">Accede con certificado</string>
<string name="unable_to_parse_certificate">Non se puido procesar o certificado</string>
<string name="mam_prefs">Gardando axustes</string>
<string name="server_side_mam_prefs">Axustes de gardado no servidor</string>
@@ -968,5 +968,4 @@
<string name="backup_started_message">Comezou a creación da copia de apoio. Recibirás unha notificación cando remate.</string>
<string name="unable_to_enable_video">Non se puido activar o vídeo.</string>
<string name="plain_text_document">Documento de texto plano</string>
-
-</resources>
+ </resources>
@@ -968,5 +968,4 @@
<string name="backup_started_message">Il backup è iniziato. Riceverai una notifica una volta completato.</string>
<string name="unable_to_enable_video">Impossibile attivare il video.</string>
<string name="plain_text_document">Documento di testo</string>
-
-</resources>
+ </resources>
@@ -950,5 +950,4 @@
<string name="backup_started_message">バックアップを開始しました。 バックアップが完了すると通知が届きます。</string>
<string name="unable_to_enable_video">映像を有効化できません。</string>
<string name="plain_text_document">プレーンテキスト文書</string>
-
-</resources>
+ </resources>
@@ -431,7 +431,7 @@
<string name="offering_x_file">Oferowanie %s</string>
<string name="hide_offline">Ukryj niedostępnych</string>
<string name="contact_is_typing">%s pisze...</string>
- <string name="contact_has_stopped_typing">%s przestał(a) pisać</string>
+ <string name="contact_has_stopped_typing">%s już nie pisze </string>
<string name="contacts_are_typing">%s piszą...</string>
<string name="contacts_have_stopped_typing">%s przestali pisać</string>
<string name="pref_chat_states">Powiadomienia pisania</string>
@@ -995,5 +995,4 @@ Administrator twojego serwera będzie mógł czytać twoje wiadomości, ale moż
<string name="backup_started_message">Tworzenie kopii zapasowej się rozpoczęło. Dostaniesz powiadomienie kiedy się zakończy. </string>
<string name="unable_to_enable_video">Nie można włączyć wideo. </string>
<string name="plain_text_document">Dokument zwykłego tekstu</string>
-
-</resources>
+ </resources>
@@ -968,5 +968,4 @@
<string name="backup_started_message">O backup foi iniciado. Você receberá uma notificação assim que ele for concluído.</string>
<string name="unable_to_enable_video">Não foi possível habilitar o vídeo.</string>
<string name="plain_text_document">Documento em texto puro</string>
-
-</resources>
+ </resources>
@@ -981,5 +981,4 @@
<string name="backup_started_message">Se creează copia de siguranță. Veți primi o notificare când acesta este completă.</string>
<string name="unable_to_enable_video">Nu s-a putut activa camera video.</string>
<string name="plain_text_document">Document text</string>
-
-</resources>
+ </resources>
@@ -993,5 +993,4 @@
<string name="backup_started_message">Резервное копирование было начато. Вы получите уведомление, как только оно будет завершено. </string>
<string name="unable_to_enable_video">Невозможно включить видео.</string>
<string name="plain_text_document">Текстовые данные</string>
-
-</resources>
+ </resources>
@@ -537,6 +537,7 @@
<string name="security_error_invalid_file_access">Säkerhetsfel: Ogiltig filåtkomst!</string>
<string name="no_application_to_share_uri">Ingen applikation hittades för att dela URI</string>
<string name="share_uri_with">Dela URI med...</string>
+ <string name="agree_and_continue">Acceptera och gå vidare</string>
<string name="your_full_jid_will_be">Din fullständiga XMPP-adress kommer att vara: %s</string>
<string name="create_account">Skapa konto</string>
<string name="use_own_provider">Använd min egen leverantör</string>
@@ -662,7 +663,9 @@
<string name="yesterday">Igår</string>
<string name="pref_validate_hostname">Bekräfta värdnamn med DNSSEC</string>
<string name="certificate_does_not_contain_jid">Certifikatet innehåller ej en XMPP-adress</string>
+ <string name="server_info_partial">delvis</string>
<string name="attach_record_video">Spela in video</string>
+ <string name="copy_to_clipboard">Kopiera till urklipp</string>
<string name="message_copied_to_clipboard">Meddelande kopierat till urklipp</string>
<string name="message">Meddelande</string>
<string name="mtm_accept_cert">Godkänn okänt certifikat?</string>
@@ -968,5 +968,4 @@
<string name="backup_started_message">Yedekleme başlatıldı. Tamamlandığı zaman bir bildirim alacaksınız.</string>
<string name="unable_to_enable_video">Video etkinleştirilemedi</string>
<string name="plain_text_document">Düz metin dosyası</string>
-
-</resources>
+ </resources>
@@ -955,5 +955,4 @@
<string name="backup_started_message">Việc sao lưu đã được bắt đầu. Bạn sẽ nhận một thông báo khi việc đó đã hoàn tất.</string>
<string name="unable_to_enable_video">Không thể bật video.</string>
<string name="plain_text_document">Tài liệu văn bản thuần</string>
-
-</resources>
+ </resources>
@@ -955,5 +955,4 @@
<string name="backup_started_message">已启动备份。一旦完成,你会收到通知。</string>
<string name="unable_to_enable_video">无法启用视频</string>
<string name="plain_text_document">纯文本文档</string>
-
-</resources>
+ </resources>
@@ -31,7 +31,7 @@
<resources>
<string name="pref_about_message" translatable="false">
Conversations • the very last word in instant messaging.
- \n\nCopyright © 2014-2021 Daniel Gultsch
+ \n\nCopyright © 2014-2022 Daniel Gultsch
\n\nThis program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
@@ -0,0 +1,10 @@
+package eu.siacs.conversations.services;
+
+import android.content.Context;
+
+public class EmojiInitializationService {
+
+ public static void execute(final Context context) {
+
+ }
+}
@@ -1,55 +0,0 @@
-package eu.siacs.conversations.ui.service;
-
-import android.content.Context;
-import android.os.Build;
-import android.util.Log;
-
-import androidx.core.provider.FontRequest;
-import androidx.emoji.text.EmojiCompat;
-import androidx.emoji.text.FontRequestEmojiCompatConfig;
-
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.R;
-
-public class EmojiService {
-
-
- private final EmojiCompat.InitCallback initCallback = new EmojiCompat.InitCallback() {
- @Override
- public void onInitialized() {
- super.onInitialized();
- Log.d(Config.LOGTAG, "EmojiService succeeded in loading fonts");
-
- }
-
- @Override
- public void onFailed(Throwable throwable) {
- super.onFailed(throwable);
- Log.d(Config.LOGTAG, "EmojiService failed to load fonts", throwable);
- }
- };
-
- private final Context context;
-
- public EmojiService(Context context) {
- this.context = context;
- }
-
- public void init() {
- final FontRequest fontRequest = new FontRequest(
- "com.google.android.gms.fonts",
- "com.google.android.gms",
- "Noto Color Emoji Compat",
- R.array.font_certs);
- FontRequestEmojiCompatConfig fontRequestEmojiCompatConfig = new FontRequestEmojiCompatConfig(context, fontRequest);
- fontRequestEmojiCompatConfig.registerInitCallback(initCallback);
- //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) when using the ondemand emoji font (play store) flags don’t work
- // b) the text preview has annoying glitches when the cut of text contains emojis (the emoji will be half visible)
- // c) can trigger a hardware rendering bug https://issuetracker.google.com/issues/67102093
- fontRequestEmojiCompatConfig.setReplaceAll(Build.VERSION.SDK_INT < Build.VERSION_CODES.O);
- EmojiCompat.init(fontRequestEmojiCompatConfig);
- }
-
-}
@@ -1,6 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<resources>
- <string-array name="font_certs">
@@ -1,14 +0,0 @@
-package eu.siacs.conversations.ui.service;
-
-import android.content.Context;
-
-public class EmojiService {
-
- public EmojiService(Context context) {
- //nop
- }
-
- public void init() {
- //nop
- }
-}
@@ -1,16 +0,0 @@
-package eu.siacs.conversations.ui.widget;
-
-import android.content.Context;
-import androidx.appcompat.widget.AppCompatEditText;
-import android.util.AttributeSet;
-
-public class EmojiWrapperEditText extends AppCompatEditText {
-
- public EmojiWrapperEditText(Context context) {
- super(context);
- }
-
- public EmojiWrapperEditText(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-}
@@ -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;
- }
- }
-}