Merge remote-tracking branch 'upstream/master'
* upstream/master: (27 commits) show 'using account …' in incoming call screen show contact jid in call screen bump copyright year Add handling of status code 333 increase default pw length do not build emoji flavors pulled translations from transifex add changelog fix ice candidate sending when different credentials are used remove security check that ensures rtp connection was properly finished code clean up bump agp store encrypted pgp files in private cache dir do not restart wakelock if activity is finishing delete pre lolipop weOwnFile() use try with resources. remove unused methods rename version suffix to playstore/free bump appcompat, migrate to emoji2 and get rid of emoji flavor fix rare npe store recordings and documents in their respective folders ...
This commit is contained in:
commit
5e149cfcd1
|
@ -28,6 +28,6 @@ tasks:
|
||||||
sed -ie 's/\/\/ INSERT/implementation "io.sentry:sentry-android:5.6.1"/' build.gradle
|
sed -ie 's/\/\/ INSERT/implementation "io.sentry:sentry-android:5.6.1"/' build.gradle
|
||||||
- build: |
|
- build: |
|
||||||
cd cheogram-android
|
cd cheogram-android
|
||||||
./gradlew assembleCheogramFreeCompatDebug
|
./gradlew assembleCheogramFreeDebug
|
||||||
- assets: |
|
- 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
|
run: mkdir libs && wget -O libs/libwebrtc-m92.aar https://gultsch.de/files/libwebrtc-m92.aar
|
||||||
- name: Grant execute permission for gradlew
|
- name: Grant execute permission for gradlew
|
||||||
run: chmod +x gradlew
|
run: chmod +x gradlew
|
||||||
- name: Build Quicksy (Compat)
|
- name: Build Quicksy
|
||||||
run: ./gradlew assembleQuicksyFreeCompatDebug
|
run: ./gradlew assembleQuicksyFreeDebug
|
||||||
- name: Build Quicksy (System)
|
- name: Build Conversations
|
||||||
run: ./gradlew assembleQuicksyFreeSystemDebug
|
run: ./gradlew assembleConversationsFreeDebug
|
||||||
- name: Build Conversations (Compat)
|
|
||||||
run: ./gradlew assembleConversationsFreeCompatDebug
|
|
||||||
- name: Build Conversations (System)
|
|
||||||
run: ./gradlew assembleConversationsFreeSystemDebug
|
|
||||||
- uses: actions/upload-artifact@v2
|
- uses: actions/upload-artifact@v2
|
||||||
with:
|
with:
|
||||||
name: Conversations all-flavors (debug)
|
name: Conversations all-flavors (debug)
|
||||||
|
|
|
@ -1,5 +1,10 @@
|
||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
### Version 2.10.3
|
||||||
|
|
||||||
|
* Store files in location appropriate for Android 11
|
||||||
|
* Attempt to reconnect call after network switch
|
||||||
|
|
||||||
### Version 2.10.2
|
### Version 2.10.2
|
||||||
|
|
||||||
* Fix crash when rendering some quotes
|
* Fix crash when rendering some quotes
|
||||||
|
|
98
build.gradle
98
build.gradle
|
@ -6,7 +6,7 @@ buildscript {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
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 {
|
configurations {
|
||||||
playstoreImplementation
|
playstoreImplementation
|
||||||
compatImplementation
|
freeImplementation
|
||||||
conversationsFreeCompatImplementation
|
conversationsFreeImplementation
|
||||||
cheogramFreeCompatImplementation
|
conversationsPlaystorImplementation
|
||||||
conversationsPlaystoreCompatImplementation
|
conversationsPlaystoreImplementation
|
||||||
conversationsPlaystoreSystemImplementation
|
quicksyPlaystoreImplementation
|
||||||
quicksyPlaystoreCompatImplementation
|
quicksyPlaystoreImplementation
|
||||||
quicksyPlaystoreSystemImplementation
|
quicksyFreeImplementation
|
||||||
quicksyFreeCompatImplementation
|
|
||||||
quicksyImplementation
|
quicksyImplementation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -57,22 +56,19 @@ dependencies {
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-analytics'
|
exclude group: 'com.google.firebase', module: 'firebase-analytics'
|
||||||
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
|
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
|
||||||
}
|
}
|
||||||
conversationsPlaystoreCompatImplementation("com.android.installreferrer:installreferrer:2.2")
|
conversationsPlaystoreImplementation("com.android.installreferrer:installreferrer:2.2")
|
||||||
conversationsPlaystoreSystemImplementation("com.android.installreferrer:installreferrer:2.2")
|
quicksyPlaystoreImplementation 'com.google.android.gms:play-services-auth-api-phone:18.0.1'
|
||||||
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'
|
|
||||||
implementation 'org.sufficientlysecure:openpgp-api:10.0'
|
implementation 'org.sufficientlysecure:openpgp-api:10.0'
|
||||||
implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.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.exifinterface:exifinterface:1.3.3'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||||
implementation 'androidx.emoji:emoji:1.1.0'
|
|
||||||
implementation 'com.google.android.material:material:1.4.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'
|
implementation "androidx.emoji2:emoji2:1.1.0-rc01"
|
||||||
cheogramFreeCompatImplementation 'androidx.emoji:emoji-bundled:1.1.0'
|
freeImplementation "androidx.emoji2:emoji2-bundled:1.1.0-rc01"
|
||||||
quicksyFreeCompatImplementation 'androidx.emoji:emoji-bundled:1.1.0'
|
|
||||||
implementation 'org.bouncycastle:bcmail-jdk15on:1.64'
|
implementation 'org.bouncycastle:bcmail-jdk15on:1.64'
|
||||||
//zxing stopped supporting Java 7 so we have to stick with 3.3.3
|
//zxing stopped supporting Java 7 so we have to stick with 3.3.3
|
||||||
//https://github.com/zxing/zxing/issues/1170
|
//https://github.com/zxing/zxing/issues/1170
|
||||||
|
@ -138,7 +134,7 @@ android {
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
|
|
||||||
flavorDimensions("mode", "distribution", "emoji")
|
flavorDimensions("mode", "distribution")
|
||||||
|
|
||||||
productFlavors {
|
productFlavors {
|
||||||
|
|
||||||
|
@ -169,45 +165,21 @@ android {
|
||||||
|
|
||||||
playstore {
|
playstore {
|
||||||
dimension "distribution"
|
dimension "distribution"
|
||||||
versionNameSuffix "+p"
|
versionNameSuffix "+playstore"
|
||||||
}
|
}
|
||||||
free {
|
free {
|
||||||
dimension "distribution"
|
dimension "distribution"
|
||||||
versionNameSuffix "+f"
|
versionNameSuffix "+free"
|
||||||
}
|
|
||||||
system {
|
|
||||||
dimension "emoji"
|
|
||||||
versionNameSuffix "s"
|
|
||||||
}
|
|
||||||
compat {
|
|
||||||
dimension "emoji"
|
|
||||||
versionNameSuffix "c"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
quicksyFreeSystem {
|
quicksyFree {
|
||||||
java {
|
java {
|
||||||
srcDir 'src/quicksyFree/java'
|
srcDir 'src/quicksyFree/java'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
quicksyFreeCompat {
|
quicksyPlaystore {
|
||||||
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 {
|
|
||||||
java {
|
java {
|
||||||
srcDir 'src/quicksyPlaystore/java'
|
srcDir 'src/quicksyPlaystore/java'
|
||||||
}
|
}
|
||||||
|
@ -215,39 +187,17 @@ android {
|
||||||
srcDir 'src/quicksyPlaystore/res'
|
srcDir 'src/quicksyPlaystore/res'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conversationsFreeCompat {
|
conversationsFree {
|
||||||
java {
|
|
||||||
srcDir 'src/freeCompat/java'
|
|
||||||
srcDir 'src/conversationsFree/java'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
conversationsFreeSystem {
|
|
||||||
java {
|
java {
|
||||||
srcDir 'src/conversationsFree/java'
|
srcDir 'src/conversationsFree/java'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cheogramFreeCompat {
|
cheogramFree {
|
||||||
java {
|
|
||||||
srcDir 'src/freeCompat/java'
|
|
||||||
srcDir 'src/conversationsFree/java'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cheogramFreeSystem {
|
|
||||||
java {
|
java {
|
||||||
srcDir 'src/conversationsFree/java'
|
srcDir 'src/conversationsFree/java'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
conversationsPlaystoreCompat {
|
conversationsPlaystore {
|
||||||
java {
|
|
||||||
srcDir 'src/playstoreCompat/java'
|
|
||||||
srcDir 'src/conversationsPlaystore/java'
|
|
||||||
}
|
|
||||||
res {
|
|
||||||
srcDir 'src/playstoreCompat/res'
|
|
||||||
srcDir 'src/conversationsPlaystore/res'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
conversationsPlaystoreSystem {
|
|
||||||
java {
|
java {
|
||||||
srcDir 'src/conversationsPlaystore/java'
|
srcDir 'src/conversationsPlaystore/java'
|
||||||
}
|
}
|
||||||
|
@ -262,13 +212,11 @@ android {
|
||||||
shrinkResources true
|
shrinkResources true
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
versionNameSuffix "r"
|
|
||||||
}
|
}
|
||||||
debug {
|
debug {
|
||||||
shrinkResources true
|
shrinkResources true
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
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 List<Jid> accounts = mDatabaseBackend.getAccountJids(false);
|
||||||
final ArrayList<BackupFile> backupFiles = new ArrayList<>();
|
final ArrayList<BackupFile> backupFiles = new ArrayList<>();
|
||||||
final Set<String> apps = new HashSet<>(Arrays.asList("Conversations", "Quicksy", getString(R.string.app_name)));
|
final Set<String> apps = new HashSet<>(Arrays.asList("Conversations", "Quicksy", getString(R.string.app_name)));
|
||||||
for (String app : apps) {
|
final List<File> directories = new ArrayList<>();
|
||||||
final File directory = new File(FileBackend.getBackupDirectory(app));
|
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()) {
|
if (!directory.exists() || !directory.isDirectory()) {
|
||||||
Log.d(Config.LOGTAG, "directory not found: " + directory.getAbsolutePath());
|
Log.d(Config.LOGTAG, "directory not found: " + directory.getAbsolutePath());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final File[] files = directory.listFiles();
|
final File[] files = directory.listFiles();
|
||||||
if (files == null) {
|
if (files == null) {
|
||||||
onBackupFilesLoaded.onBackupFilesLoaded(backupFiles);
|
continue;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
for (final File file : files) {
|
for (final File file : files) {
|
||||||
if (file.isFile() && file.getName().endsWith(".ceb")) {
|
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 List<Jid> accounts = mDatabaseBackend.getAccountJids(false);
|
||||||
final ArrayList<BackupFile> backupFiles = new ArrayList<>();
|
final ArrayList<BackupFile> backupFiles = new ArrayList<>();
|
||||||
final Set<String> apps = new HashSet<>(Arrays.asList("Conversations", "Quicksy", getString(R.string.app_name)));
|
final Set<String> apps = new HashSet<>(Arrays.asList("Conversations", "Quicksy", getString(R.string.app_name)));
|
||||||
for (String app : apps) {
|
final List<File> directories = new ArrayList<>();
|
||||||
final File directory = new File(FileBackend.getBackupDirectory(app));
|
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()) {
|
if (!directory.exists() || !directory.isDirectory()) {
|
||||||
Log.d(Config.LOGTAG, "directory not found: " + directory.getAbsolutePath());
|
Log.d(Config.LOGTAG, "directory not found: " + directory.getAbsolutePath());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
final File[] files = directory.listFiles();
|
final File[] files = directory.listFiles();
|
||||||
if (files == null) {
|
if (files == null) {
|
||||||
onBackupFilesLoaded.onBackupFilesLoaded(backupFiles);
|
continue;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
for (final File file : files) {
|
for (final File file : files) {
|
||||||
if (file.isFile() && file.getName().endsWith(".ceb")) {
|
if (file.isFile() && file.getName().endsWith(".ceb")) {
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<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="use_conversations.im">Utilizar conversations.im</string>
|
||||||
<string name="create_new_account">Crear nova conta</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="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="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_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. 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_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="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="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="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="easy_invite_share_text">Únete a %1$s e conversa conmigo: %2$s</string>
|
||||||
<string name="share_invite_with">Enviar convite a...</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:name="android.hardware.microphone"
|
||||||
android:required="false" />
|
android:required="false" />
|
||||||
|
|
||||||
|
<queries>
|
||||||
|
<package android:name="org.sufficientlysecure.keychain"/>
|
||||||
|
</queries>
|
||||||
|
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
|
@ -61,6 +65,7 @@
|
||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
android:networkSecurityConfig="@xml/network_security_configuration"
|
android:networkSecurityConfig="@xml/network_security_configuration"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
|
android:preserveLegacyExternalStorage="true"
|
||||||
android:theme="@style/ConversationsTheme"
|
android:theme="@style/ConversationsTheme"
|
||||||
tools:replace="android:label"
|
tools:replace="android:label"
|
||||||
tools:targetApi="q">
|
tools:targetApi="q">
|
||||||
|
|
|
@ -9,6 +9,7 @@ import org.openintents.openpgp.util.OpenPgpApi;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -147,9 +148,6 @@ public class PgpDecryptionService {
|
||||||
try {
|
try {
|
||||||
os.flush();
|
os.flush();
|
||||||
final String body = os.toString();
|
final String body = os.toString();
|
||||||
if (body == null) {
|
|
||||||
throw new IOException("body was null");
|
|
||||||
}
|
|
||||||
message.setBody(body);
|
message.setBody(body);
|
||||||
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
|
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
|
||||||
final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager();
|
final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager();
|
||||||
|
@ -194,9 +192,9 @@ public class PgpDecryptionService {
|
||||||
String originalExtension = originalFilename == null ? null : MimeUtils.extractRelevantExtension(originalFilename);
|
String originalExtension = originalFilename == null ? null : MimeUtils.extractRelevantExtension(originalFilename);
|
||||||
if (originalExtension != null && MimeUtils.extractRelevantExtension(outputFile.getName()) == null) {
|
if (originalExtension != null && MimeUtils.extractRelevantExtension(outputFile.getName()) == null) {
|
||||||
Log.d(Config.LOGTAG,"detected original filename during pgp decryption");
|
Log.d(Config.LOGTAG,"detected original filename during pgp decryption");
|
||||||
String mime = MimeUtils.guessMimeTypeFromExtension(originalExtension);
|
final String mime = MimeUtils.guessMimeTypeFromExtension(originalExtension);
|
||||||
String path = outputFile.getName()+"."+originalExtension;
|
final String filename = outputFile.getName()+"."+originalExtension;
|
||||||
DownloadableFile fixedFile = mXmppConnectionService.getFileBackend().getFileForPath(path,mime);
|
final File fixedFile = mXmppConnectionService.getFileBackend().getStorageLocation(filename,mime);
|
||||||
if (fixedFile.getParentFile().mkdirs()) {
|
if (fixedFile.getParentFile().mkdirs()) {
|
||||||
Log.d(Config.LOGTAG,"created parent directories for "+fixedFile.getAbsolutePath());
|
Log.d(Config.LOGTAG,"created parent directories for "+fixedFile.getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
@ -205,7 +203,7 @@ public class PgpDecryptionService {
|
||||||
}
|
}
|
||||||
if (outputFile.renameTo(fixedFile)) {
|
if (outputFile.renameTo(fixedFile)) {
|
||||||
Log.d(Config.LOGTAG, "renamed " + outputFile.getAbsolutePath() + " to " + fixedFile.getAbsolutePath());
|
Log.d(Config.LOGTAG, "renamed " + outputFile.getAbsolutePath() + " to " + fixedFile.getAbsolutePath());
|
||||||
message.setRelativeFilePath(path);
|
message.setRelativeFilePath(fixedFile.getAbsolutePath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
final String url = message.getFileParams().url;
|
final String url = message.getFileParams().url;
|
||||||
|
|
|
@ -16,6 +16,10 @@ public class DownloadableFile extends File {
|
||||||
private byte[] aeskey;
|
private byte[] aeskey;
|
||||||
private byte[] iv;
|
private byte[] iv;
|
||||||
|
|
||||||
|
public DownloadableFile(final File parent, final String file) {
|
||||||
|
super(parent, file);
|
||||||
|
}
|
||||||
|
|
||||||
public DownloadableFile(String path) {
|
public DownloadableFile(String path) {
|
||||||
super(path);
|
super(path);
|
||||||
}
|
}
|
||||||
|
|
|
@ -96,11 +96,8 @@ public class HttpDownloadConnection implements Transferable {
|
||||||
this.message.setEncryption(Message.ENCRYPTION_NONE);
|
this.message.setEncryption(Message.ENCRYPTION_NONE);
|
||||||
}
|
}
|
||||||
final String ext = extension.getExtension();
|
final String ext = extension.getExtension();
|
||||||
if (ext != null) {
|
final String filename = Strings.isNullOrEmpty(ext) ? message.getUuid() : String.format("%s.%s", message.getUuid(), ext);
|
||||||
message.setRelativeFilePath(String.format("%s.%s", message.getUuid(), ext));
|
mXmppConnectionService.getFileBackend().setupRelativeFilePath(message, filename);
|
||||||
} else if (Strings.isNullOrEmpty(message.getRelativeFilePath())) {
|
|
||||||
message.setRelativeFilePath(message.getUuid());
|
|
||||||
}
|
|
||||||
setupFile();
|
setupFile();
|
||||||
if (this.message.getEncryption() == Message.ENCRYPTION_AXOLOTL && this.file.getKey() == null) {
|
if (this.message.getEncryption() == Message.ENCRYPTION_AXOLOTL && this.file.getKey() == null) {
|
||||||
this.message.setEncryption(Message.ENCRYPTION_NONE);
|
this.message.setEncryption(Message.ENCRYPTION_NONE);
|
||||||
|
@ -122,7 +119,7 @@ public class HttpDownloadConnection implements Transferable {
|
||||||
private void setupFile() {
|
private void setupFile() {
|
||||||
final String reference = mUrl.fragment();
|
final String reference = mUrl.fragment();
|
||||||
if (reference != null && AesGcmURL.IV_KEY.matcher(reference).matches()) {
|
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));
|
this.file.setKeyAndIv(CryptoHelper.hexToBytes(reference));
|
||||||
Log.d(Config.LOGTAG, "create temporary OMEMO encrypted file: " + this.file.getAbsolutePath() + "(" + message.getMimeType() + ")");
|
Log.d(Config.LOGTAG, "create temporary OMEMO encrypted file: " + this.file.getAbsolutePath() + "(" + message.getMimeType() + ")");
|
||||||
} else {
|
} else {
|
||||||
|
@ -326,7 +323,7 @@ public class HttpDownloadConnection implements Transferable {
|
||||||
if (Strings.isNullOrEmpty(extension.getExtension()) && contentType != null) {
|
if (Strings.isNullOrEmpty(extension.getExtension()) && contentType != null) {
|
||||||
final String fileExtension = MimeUtils.guessExtensionFromMimeType(contentType);
|
final String fileExtension = MimeUtils.guessExtensionFromMimeType(contentType);
|
||||||
if (fileExtension != null) {
|
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");
|
Log.d(Config.LOGTAG, "rewriting name after not finding extension in url but in content type");
|
||||||
setupFile();
|
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 + ")");
|
Log.d(Config.LOGTAG, "content-length reported on GET (" + size + ") did not match Content-Length reported on HEAD (" + expected + ")");
|
||||||
}
|
}
|
||||||
file.getParentFile().mkdirs();
|
file.getParentFile().mkdirs();
|
||||||
|
Log.d(Config.LOGTAG,"creating file: "+file.getAbsolutePath());
|
||||||
if (!file.exists() && !file.createNewFile()) {
|
if (!file.exists() && !file.createNewFile()) {
|
||||||
throw new FileWriterException();
|
throw new FileWriterException(file);
|
||||||
}
|
}
|
||||||
outputStream = AbstractConnectionManager.createOutputStream(file, false, false);
|
outputStream = AbstractConnectionManager.createOutputStream(file, false, false);
|
||||||
}
|
}
|
||||||
|
@ -431,7 +429,7 @@ public class HttpDownloadConnection implements Transferable {
|
||||||
try {
|
try {
|
||||||
outputStream.write(buffer, 0, count);
|
outputStream.write(buffer, 0, count);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new FileWriterException();
|
throw new FileWriterException(file);
|
||||||
}
|
}
|
||||||
updateProgress(Math.round(((double) transmitted / expected) * 100));
|
updateProgress(Math.round(((double) transmitted / expected) * 100));
|
||||||
}
|
}
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -91,7 +91,7 @@ public class AttachFileToConversationRunnable implements Runnable, TranscoderLis
|
||||||
private void processAsVideo() throws FileNotFoundException {
|
private void processAsVideo() throws FileNotFoundException {
|
||||||
Log.d(Config.LOGTAG, "processing file as video");
|
Log.d(Config.LOGTAG, "processing file as video");
|
||||||
mXmppConnectionService.startForcingForegroundNotification();
|
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);
|
final DownloadableFile file = mXmppConnectionService.getFileBackend().getFile(message);
|
||||||
if (Objects.requireNonNull(file.getParentFile()).mkdirs()) {
|
if (Objects.requireNonNull(file.getParentFile()).mkdirs()) {
|
||||||
Log.d(Config.LOGTAG, "created parent directory for video file");
|
Log.d(Config.LOGTAG, "created parent directory for video file");
|
||||||
|
|
|
@ -291,7 +291,7 @@ public class ExportBackupService extends Service {
|
||||||
secureRandom.nextBytes(salt);
|
secureRandom.nextBytes(salt);
|
||||||
final BackupFileHeader backupFileHeader = new BackupFileHeader(getString(R.string.app_name), account.getJid(), System.currentTimeMillis(), IV, 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 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);
|
files.add(file);
|
||||||
final File directory = file.getParentFile();
|
final File directory = file.getParentFile();
|
||||||
if (directory != null && directory.mkdirs()) {
|
if (directory != null && directory.mkdirs()) {
|
||||||
|
@ -335,7 +335,7 @@ public class ExportBackupService extends Service {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void notifySuccess(final List<File> files) {
|
private void notifySuccess(final List<File> files) {
|
||||||
final String path = FileBackend.getBackupDirectory(this);
|
final String path = FileBackend.getBackupDirectory(this).getAbsolutePath();
|
||||||
|
|
||||||
PendingIntent openFolderIntent = null;
|
PendingIntent openFolderIntent = null;
|
||||||
|
|
||||||
|
@ -363,7 +363,7 @@ public class ExportBackupService extends Service {
|
||||||
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup");
|
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup");
|
||||||
mBuilder.setContentTitle(getString(R.string.notification_backup_created_title))
|
mBuilder.setContentTitle(getString(R.string.notification_backup_created_title))
|
||||||
.setContentText(getString(R.string.notification_backup_created_subtitle, path))
|
.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)
|
.setAutoCancel(true)
|
||||||
.setContentIntent(openFolderIntent)
|
.setContentIntent(openFolderIntent)
|
||||||
.setSmallIcon(R.drawable.ic_archive_white_24dp);
|
.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.ui.util.SoftKeyboardUtils;
|
||||||
import eu.siacs.conversations.utils.AccountUtils;
|
import eu.siacs.conversations.utils.AccountUtils;
|
||||||
import eu.siacs.conversations.utils.Compatibility;
|
import eu.siacs.conversations.utils.Compatibility;
|
||||||
import eu.siacs.conversations.utils.EmojiWrapper;
|
|
||||||
import eu.siacs.conversations.utils.StringUtils;
|
import eu.siacs.conversations.utils.StringUtils;
|
||||||
import eu.siacs.conversations.utils.StylingHelper;
|
import eu.siacs.conversations.utils.StylingHelper;
|
||||||
import eu.siacs.conversations.utils.XmppUri;
|
import eu.siacs.conversations.utils.XmppUri;
|
||||||
|
@ -471,11 +470,11 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
|
||||||
String subject = mucOptions.getSubject();
|
String subject = mucOptions.getSubject();
|
||||||
final boolean hasTitle;
|
final boolean hasTitle;
|
||||||
if (printableValue(roomName)) {
|
if (printableValue(roomName)) {
|
||||||
this.binding.mucTitle.setText(EmojiWrapper.transform(roomName));
|
this.binding.mucTitle.setText(roomName);
|
||||||
this.binding.mucTitle.setVisibility(View.VISIBLE);
|
this.binding.mucTitle.setVisibility(View.VISIBLE);
|
||||||
hasTitle = true;
|
hasTitle = true;
|
||||||
} else if (!printableValue(subject)) {
|
} else if (!printableValue(subject)) {
|
||||||
this.binding.mucTitle.setText(EmojiWrapper.transform(mConversation.getName()));
|
this.binding.mucTitle.setText(mConversation.getName());
|
||||||
hasTitle = true;
|
hasTitle = true;
|
||||||
this.binding.mucTitle.setVisibility(View.VISIBLE);
|
this.binding.mucTitle.setVisibility(View.VISIBLE);
|
||||||
} else {
|
} else {
|
||||||
|
@ -486,7 +485,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
|
||||||
SpannableStringBuilder spannable = new SpannableStringBuilder(subject);
|
SpannableStringBuilder spannable = new SpannableStringBuilder(subject);
|
||||||
StylingHelper.format(spannable, this.binding.mucSubject.getCurrentTextColor());
|
StylingHelper.format(spannable, this.binding.mucSubject.getCurrentTextColor());
|
||||||
MyLinkify.addLinks(spannable, false);
|
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.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.setAutoLinkMask(0);
|
||||||
this.binding.mucSubject.setVisibility(View.VISIBLE);
|
this.binding.mucSubject.setVisibility(View.VISIBLE);
|
||||||
|
|
|
@ -6,6 +6,7 @@ import android.app.Activity;
|
||||||
import android.app.Fragment;
|
import android.app.Fragment;
|
||||||
import android.app.FragmentManager;
|
import android.app.FragmentManager;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
|
import android.content.ActivityNotFoundException;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
@ -1183,8 +1184,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
||||||
cancelTransmission.setVisible(true);
|
cancelTransmission.setVisible(true);
|
||||||
}
|
}
|
||||||
if (m.isFileOrImage() && !deleted && !cancelable) {
|
if (m.isFileOrImage() && !deleted && !cancelable) {
|
||||||
String path = m.getRelativeFilePath();
|
final String path = m.getRelativeFilePath();
|
||||||
if (path == null || !path.startsWith("/") || FileBackend.isInDirectoryThatShouldNotBeScanned(getActivity(), path)) {
|
if (path == null || !path.startsWith("/")) {
|
||||||
deleteFile.setVisible(true);
|
deleteFile.setVisible(true);
|
||||||
deleteFile.setTitle(activity.getString(R.string.delete_x_file, UIHelper.getFileDescriptionString(activity, m)));
|
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) {
|
if (context == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (intent.resolveActivity(context.getPackageManager()) != null) {
|
try {
|
||||||
if (chooser) {
|
if (chooser) {
|
||||||
startActivityForResult(
|
startActivityForResult(
|
||||||
Intent.createChooser(intent, getString(R.string.perform_action_with)),
|
Intent.createChooser(intent, getString(R.string.perform_action_with)),
|
||||||
|
@ -1752,7 +1753,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
||||||
} else {
|
} else {
|
||||||
startActivityForResult(intent, attachmentChoice);
|
startActivityForResult(intent, attachmentChoice);
|
||||||
}
|
}
|
||||||
} else {
|
} catch (final ActivityNotFoundException e) {
|
||||||
Toast.makeText(context, R.string.no_application_found, Toast.LENGTH_LONG).show();
|
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) {
|
private List<Uri> cleanUris(final List<Uri> uris) {
|
||||||
Iterator<Uri> iterator = uris.iterator();
|
final Iterator<Uri> iterator = uris.iterator();
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
final Uri uri = iterator.next();
|
final Uri uri = iterator.next();
|
||||||
if (FileBackend.weOwnFile(getActivity(), uri)) {
|
if (FileBackend.weOwnFile(uri)) {
|
||||||
iterator.remove();
|
iterator.remove();
|
||||||
Toast.makeText(getActivity(), R.string.security_violation_not_attaching_file, Toast.LENGTH_SHORT).show();
|
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.ConversationMenuConfigurator;
|
||||||
import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
|
import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
|
||||||
import eu.siacs.conversations.ui.util.PendingItem;
|
import eu.siacs.conversations.ui.util.PendingItem;
|
||||||
import eu.siacs.conversations.utils.EmojiWrapper;
|
|
||||||
import eu.siacs.conversations.utils.ExceptionHelper;
|
import eu.siacs.conversations.utils.ExceptionHelper;
|
||||||
import eu.siacs.conversations.utils.SignupUtils;
|
import eu.siacs.conversations.utils.SignupUtils;
|
||||||
import eu.siacs.conversations.utils.XmppUri;
|
import eu.siacs.conversations.utils.XmppUri;
|
||||||
|
@ -615,7 +614,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
||||||
if (mainFragment instanceof ConversationFragment) {
|
if (mainFragment instanceof ConversationFragment) {
|
||||||
final Conversation conversation = ((ConversationFragment) mainFragment).getConversation();
|
final Conversation conversation = ((ConversationFragment) mainFragment).getConversation();
|
||||||
if (conversation != null) {
|
if (conversation != null) {
|
||||||
actionBar.setTitle(EmojiWrapper.transform(conversation.getName()));
|
actionBar.setTitle(conversation.getName());
|
||||||
actionBar.setDisplayHomeAsUpEnabled(true);
|
actionBar.setDisplayHomeAsUpEnabled(true);
|
||||||
ActionBarUtil.setActionBarOnClickListener(
|
ActionBarUtil.setActionBarOnClickListener(
|
||||||
binding.toolbar,
|
binding.toolbar,
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.util.List;
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.databinding.EnterJidDialogBinding;
|
import eu.siacs.conversations.databinding.EnterJidDialogBinding;
|
||||||
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
|
import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
|
||||||
import eu.siacs.conversations.ui.interfaces.OnBackendConnected;
|
import eu.siacs.conversations.ui.interfaces.OnBackendConnected;
|
||||||
import eu.siacs.conversations.ui.util.DelayedHintHelper;
|
import eu.siacs.conversations.ui.util.DelayedHintHelper;
|
||||||
|
@ -29,8 +30,8 @@ import eu.siacs.conversations.xmpp.Jid;
|
||||||
|
|
||||||
public class EnterJidDialog extends DialogFragment implements OnBackendConnected, TextWatcher {
|
public class EnterJidDialog extends DialogFragment implements OnBackendConnected, TextWatcher {
|
||||||
|
|
||||||
|
private static final List<String> SUSPICIOUS_DOMAINS =
|
||||||
private static final List<String> SUSPICIOUS_DOMAINS = Arrays.asList("conference","muc","room","rooms","chat");
|
Arrays.asList("conference", "muc", "room", "rooms", "chat");
|
||||||
|
|
||||||
private OnEnterJidDialogPositiveListener mListener = null;
|
private OnEnterJidDialogPositiveListener mListener = null;
|
||||||
|
|
||||||
|
@ -49,13 +50,16 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected
|
||||||
private AlertDialog dialog;
|
private AlertDialog dialog;
|
||||||
private boolean sanityCheckJid = false;
|
private boolean sanityCheckJid = false;
|
||||||
|
|
||||||
|
|
||||||
private boolean issuedWarning = false;
|
private boolean issuedWarning = false;
|
||||||
|
|
||||||
public static EnterJidDialog newInstance(final List<String> activatedAccounts,
|
public static EnterJidDialog newInstance(
|
||||||
final String title, final String positiveButton,
|
final List<String> activatedAccounts,
|
||||||
final String prefilledJid, final String account,
|
final String title,
|
||||||
boolean allowEditJid, final boolean sanity_check_jid) {
|
final String positiveButton,
|
||||||
|
final String prefilledJid,
|
||||||
|
final String account,
|
||||||
|
boolean allowEditJid,
|
||||||
|
final boolean sanity_check_jid) {
|
||||||
EnterJidDialog dialog = new EnterJidDialog();
|
EnterJidDialog dialog = new EnterJidDialog();
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putString(TITLE_KEY, title);
|
bundle.putString(TITLE_KEY, title);
|
||||||
|
@ -79,7 +83,8 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
final Activity activity = getActivity();
|
final Activity activity = getActivity();
|
||||||
if (activity instanceof XmppActivity && ((XmppActivity) activity).xmppConnectionService != null) {
|
if (activity instanceof XmppActivity
|
||||||
|
&& ((XmppActivity) activity).xmppConnectionService != null) {
|
||||||
refreshKnownHosts();
|
refreshKnownHosts();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -89,7 +94,9 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected
|
||||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||||
final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||||
builder.setTitle(getArguments().getString(TITLE_KEY));
|
builder.setTitle(getArguments().getString(TITLE_KEY));
|
||||||
binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.enter_jid_dialog, null, false);
|
binding =
|
||||||
|
DataBindingUtil.inflate(
|
||||||
|
getActivity().getLayoutInflater(), R.layout.enter_jid_dialog, null, false);
|
||||||
this.knownHostsAdapter = new KnownHostsAdapter(getActivity(), R.layout.simple_list_item);
|
this.knownHostsAdapter = new KnownHostsAdapter(getActivity(), R.layout.simple_list_item);
|
||||||
binding.jid.setAdapter(this.knownHostsAdapter);
|
binding.jid.setAdapter(this.knownHostsAdapter);
|
||||||
binding.jid.addTextChangedListener(this);
|
binding.jid.addTextChangedListener(this);
|
||||||
|
@ -109,28 +116,31 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected
|
||||||
|
|
||||||
String account = getArguments().getString(ACCOUNT_KEY);
|
String account = getArguments().getString(ACCOUNT_KEY);
|
||||||
if (account == null) {
|
if (account == null) {
|
||||||
StartConversationActivity.populateAccountSpinner(getActivity(), getArguments().getStringArrayList(ACCOUNTS_LIST_KEY), binding.account);
|
StartConversationActivity.populateAccountSpinner(
|
||||||
|
getActivity(),
|
||||||
|
getArguments().getStringArrayList(ACCOUNTS_LIST_KEY),
|
||||||
|
binding.account);
|
||||||
} else {
|
} else {
|
||||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(),
|
ArrayAdapter<String> adapter =
|
||||||
R.layout.simple_list_item,
|
new ArrayAdapter<>(
|
||||||
new String[]{account});
|
getActivity(), R.layout.simple_list_item, new String[] {account});
|
||||||
binding.account.setEnabled(false);
|
binding.account.setEnabled(false);
|
||||||
adapter.setDropDownViewResource(R.layout.simple_list_item);
|
adapter.setDropDownViewResource(R.layout.simple_list_item);
|
||||||
binding.account.setAdapter(adapter);
|
binding.account.setAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
builder.setView(binding.getRoot());
|
builder.setView(binding.getRoot());
|
||||||
builder.setNegativeButton(R.string.cancel, null);
|
builder.setNegativeButton(R.string.cancel, null);
|
||||||
builder.setPositiveButton(getArguments().getString(POSITIVE_BUTTON_KEY), null);
|
builder.setPositiveButton(getArguments().getString(POSITIVE_BUTTON_KEY), null);
|
||||||
this.dialog = builder.create();
|
this.dialog = builder.create();
|
||||||
|
|
||||||
View.OnClickListener dialogOnClick = v -> {
|
View.OnClickListener dialogOnClick =
|
||||||
|
v -> {
|
||||||
handleEnter(binding, account);
|
handleEnter(binding, account);
|
||||||
};
|
};
|
||||||
|
|
||||||
binding.jid.setOnEditorActionListener((v, actionId, event) -> {
|
binding.jid.setOnEditorActionListener(
|
||||||
|
(v, actionId, event) -> {
|
||||||
handleEnter(binding, account);
|
handleEnter(binding, account);
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
@ -147,7 +157,11 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
if (Config.DOMAIN_LOCK != null) {
|
if (Config.DOMAIN_LOCK != null) {
|
||||||
accountJid = Jid.ofEscaped((String) binding.account.getSelectedItem(), Config.DOMAIN_LOCK, null);
|
accountJid =
|
||||||
|
Jid.ofEscaped(
|
||||||
|
(String) binding.account.getSelectedItem(),
|
||||||
|
Config.DOMAIN_LOCK,
|
||||||
|
null);
|
||||||
} else {
|
} else {
|
||||||
accountJid = Jid.ofEscaped((String) binding.account.getSelectedItem());
|
accountJid = Jid.ofEscaped((String) binding.account.getSelectedItem());
|
||||||
}
|
}
|
||||||
|
@ -164,13 +178,15 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected
|
||||||
|
|
||||||
if (!issuedWarning && sanityCheckJid) {
|
if (!issuedWarning && sanityCheckJid) {
|
||||||
if (contactJid.isDomainJid()) {
|
if (contactJid.isDomainJid()) {
|
||||||
binding.jidLayout.setError(getActivity().getString(R.string.this_looks_like_a_domain));
|
binding.jidLayout.setError(
|
||||||
|
getActivity().getString(R.string.this_looks_like_a_domain));
|
||||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.add_anway);
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.add_anway);
|
||||||
issuedWarning = true;
|
issuedWarning = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (suspiciousSubDomain(contactJid.getDomain().toEscapedString())) {
|
if (suspiciousSubDomain(contactJid.getDomain().toEscapedString())) {
|
||||||
binding.jidLayout.setError(getActivity().getString(R.string.this_looks_like_channel));
|
binding.jidLayout.setError(
|
||||||
|
getActivity().getString(R.string.this_looks_like_channel));
|
||||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.add_anway);
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setText(R.string.add_anway);
|
||||||
issuedWarning = true;
|
issuedWarning = true;
|
||||||
return;
|
return;
|
||||||
|
@ -200,23 +216,23 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshKnownHosts() {
|
private void refreshKnownHosts() {
|
||||||
Activity activity = getActivity();
|
final Activity activity = getActivity();
|
||||||
if (activity instanceof XmppActivity) {
|
if (activity instanceof XmppActivity) {
|
||||||
Collection<String> hosts = ((XmppActivity) activity).xmppConnectionService.getKnownHosts();
|
final XmppConnectionService service = ((XmppActivity) activity).xmppConnectionService;
|
||||||
|
if (service == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final Collection<String> hosts = service.getKnownHosts();
|
||||||
this.knownHostsAdapter.refresh(hosts);
|
this.knownHostsAdapter.refresh(hosts);
|
||||||
this.whitelistedDomains = hosts;
|
this.whitelistedDomains = hosts;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterTextChanged(Editable s) {
|
public void afterTextChanged(Editable s) {
|
||||||
|
@ -238,6 +254,7 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected
|
||||||
this.msg = msg;
|
this.msg = msg;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return msg;
|
return msg;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
package eu.siacs.conversations.ui;
|
package eu.siacs.conversations.ui;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.media.MediaRecorder;
|
import android.media.MediaRecorder;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Environment;
|
||||||
import android.os.FileObserver;
|
import android.os.FileObserver;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
|
@ -17,25 +18,22 @@ import android.widget.Toast;
|
||||||
import androidx.databinding.DataBindingUtil;
|
import androidx.databinding.DataBindingUtil;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.databinding.ActivityRecordingBinding;
|
import eu.siacs.conversations.databinding.ActivityRecordingBinding;
|
||||||
import eu.siacs.conversations.persistance.FileBackend;
|
|
||||||
import eu.siacs.conversations.ui.util.SettingsUtils;
|
import eu.siacs.conversations.ui.util.SettingsUtils;
|
||||||
import eu.siacs.conversations.utils.ThemeHelper;
|
import eu.siacs.conversations.utils.ThemeHelper;
|
||||||
import eu.siacs.conversations.utils.TimeFrameUtils;
|
import eu.siacs.conversations.utils.TimeFrameUtils;
|
||||||
|
|
||||||
public class RecordingActivity extends Activity implements View.OnClickListener {
|
public class RecordingActivity extends Activity implements View.OnClickListener {
|
||||||
|
|
||||||
public static String STORAGE_DIRECTORY_TYPE_NAME = "Recordings";
|
|
||||||
|
|
||||||
private ActivityRecordingBinding binding;
|
private ActivityRecordingBinding binding;
|
||||||
|
|
||||||
private MediaRecorder mRecorder;
|
private MediaRecorder mRecorder;
|
||||||
|
@ -44,7 +42,8 @@ public class RecordingActivity extends Activity implements View.OnClickListener
|
||||||
private final CountDownLatch outputFileWrittenLatch = new CountDownLatch(1);
|
private final CountDownLatch outputFileWrittenLatch = new CountDownLatch(1);
|
||||||
|
|
||||||
private final Handler mHandler = new Handler();
|
private final Handler mHandler = new Handler();
|
||||||
private final Runnable mTickExecutor = new Runnable() {
|
private final Runnable mTickExecutor =
|
||||||
|
new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
tick();
|
tick();
|
||||||
|
@ -137,52 +136,65 @@ public class RecordingActivity extends Activity implements View.OnClickListener
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (saveFile) {
|
if (saveFile) {
|
||||||
new Thread(() -> {
|
new Thread(
|
||||||
|
() -> {
|
||||||
try {
|
try {
|
||||||
if (!outputFileWrittenLatch.await(2, TimeUnit.SECONDS)) {
|
if (!outputFileWrittenLatch.await(2, TimeUnit.SECONDS)) {
|
||||||
Log.d(Config.LOGTAG, "time out waiting for output file to be written");
|
Log.d(
|
||||||
|
Config.LOGTAG,
|
||||||
|
"time out waiting for output file to be written");
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Log.d(Config.LOGTAG, "interrupted while waiting for output file to be written", e);
|
Log.d(
|
||||||
|
Config.LOGTAG,
|
||||||
|
"interrupted while waiting for output file to be written",
|
||||||
|
e);
|
||||||
}
|
}
|
||||||
runOnUiThread(() -> {
|
runOnUiThread(
|
||||||
setResult(Activity.RESULT_OK, new Intent().setData(Uri.fromFile(mOutputFile)));
|
() -> {
|
||||||
|
setResult(
|
||||||
|
Activity.RESULT_OK,
|
||||||
|
new Intent()
|
||||||
|
.setData(Uri.fromFile(mOutputFile)));
|
||||||
finish();
|
finish();
|
||||||
});
|
});
|
||||||
}).start();
|
})
|
||||||
|
.start();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static File generateOutputFilename(Context context) {
|
private File generateOutputFilename() {
|
||||||
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
|
final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
|
||||||
String filename = "RECORDING_" + dateFormat.format(new Date()) + ".m4a";
|
final String filename = "RECORDING_" + dateFormat.format(new Date()) + ".m4a";
|
||||||
return new File(FileBackend.getConversationsDirectory(context, STORAGE_DIRECTORY_TYPE_NAME) + "/" + filename);
|
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() {
|
private void setupOutputFile() {
|
||||||
mOutputFile = generateOutputFilename(this);
|
mOutputFile = generateOutputFilename();
|
||||||
File parentDirectory = mOutputFile.getParentFile();
|
final File parentDirectory = mOutputFile.getParentFile();
|
||||||
if (parentDirectory.mkdirs()) {
|
if (Objects.requireNonNull(parentDirectory).mkdirs()) {
|
||||||
Log.d(Config.LOGTAG, "created " + parentDirectory.getAbsolutePath());
|
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);
|
setupFileObserver(parentDirectory);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupFileObserver(File directory) {
|
private void setupFileObserver(File directory) {
|
||||||
mFileObserver = new FileObserver(directory.getAbsolutePath()) {
|
mFileObserver =
|
||||||
|
new FileObserver(directory.getAbsolutePath()) {
|
||||||
@Override
|
@Override
|
||||||
public void onEvent(int event, String s) {
|
public void onEvent(int event, String s) {
|
||||||
if (s != null && s.equals(mOutputFile.getName()) && event == FileObserver.CLOSE_WRITE) {
|
if (s != null
|
||||||
|
&& s.equals(mOutputFile.getName())
|
||||||
|
&& event == FileObserver.CLOSE_WRITE) {
|
||||||
outputFileWrittenLatch.countDown();
|
outputFileWrittenLatch.countDown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -71,10 +71,9 @@ import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
|
||||||
import eu.siacs.conversations.xmpp.jingle.Media;
|
import eu.siacs.conversations.xmpp.jingle.Media;
|
||||||
import eu.siacs.conversations.xmpp.jingle.RtpEndUserState;
|
import eu.siacs.conversations.xmpp.jingle.RtpEndUserState;
|
||||||
|
|
||||||
import static eu.siacs.conversations.utils.PermissionUtils.getFirstDenied;
|
public class RtpSessionActivity extends XmppActivity
|
||||||
import static java.util.Arrays.asList;
|
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_WITH = "with";
|
||||||
public static final String EXTRA_SESSION_ID = "session_id";
|
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;
|
private static final int CALL_DURATION_UPDATE_INTERVAL = 333;
|
||||||
|
|
||||||
public static final List<RtpEndUserState> END_CARD = Arrays.asList(
|
public static final List<RtpEndUserState> END_CARD =
|
||||||
|
Arrays.asList(
|
||||||
RtpEndUserState.APPLICATION_ERROR,
|
RtpEndUserState.APPLICATION_ERROR,
|
||||||
RtpEndUserState.SECURITY_ERROR,
|
RtpEndUserState.SECURITY_ERROR,
|
||||||
RtpEndUserState.DECLINED_OR_BUSY,
|
RtpEndUserState.DECLINED_OR_BUSY,
|
||||||
RtpEndUserState.CONNECTIVITY_ERROR,
|
RtpEndUserState.CONNECTIVITY_ERROR,
|
||||||
RtpEndUserState.CONNECTIVITY_LOST_ERROR,
|
RtpEndUserState.CONNECTIVITY_LOST_ERROR,
|
||||||
RtpEndUserState.RETRACTED
|
RtpEndUserState.RETRACTED);
|
||||||
);
|
private static final List<RtpEndUserState> STATES_SHOWING_HELP_BUTTON =
|
||||||
private static final List<RtpEndUserState> STATES_SHOWING_HELP_BUTTON = Arrays.asList(
|
Arrays.asList(
|
||||||
RtpEndUserState.APPLICATION_ERROR,
|
RtpEndUserState.APPLICATION_ERROR,
|
||||||
RtpEndUserState.CONNECTIVITY_ERROR,
|
RtpEndUserState.CONNECTIVITY_ERROR,
|
||||||
RtpEndUserState.SECURITY_ERROR
|
RtpEndUserState.SECURITY_ERROR);
|
||||||
);
|
private static final List<RtpEndUserState> STATES_SHOWING_SWITCH_TO_CHAT =
|
||||||
private static final List<RtpEndUserState> STATES_SHOWING_SWITCH_TO_CHAT = Arrays.asList(
|
Arrays.asList(
|
||||||
RtpEndUserState.CONNECTING,
|
RtpEndUserState.CONNECTING,
|
||||||
RtpEndUserState.CONNECTED,
|
RtpEndUserState.CONNECTED,
|
||||||
RtpEndUserState.RECONNECTING
|
RtpEndUserState.RECONNECTING);
|
||||||
);
|
private static final List<RtpEndUserState> STATES_CONSIDERED_CONNECTED =
|
||||||
private static final List<RtpEndUserState> STATES_CONSIDERED_CONNECTED = Arrays.asList(
|
Arrays.asList(RtpEndUserState.CONNECTED, RtpEndUserState.RECONNECTING);
|
||||||
RtpEndUserState.CONNECTED,
|
private static final List<RtpEndUserState> STATES_SHOWING_PIP_PLACEHOLDER =
|
||||||
RtpEndUserState.RECONNECTING
|
Arrays.asList(
|
||||||
);
|
|
||||||
private static final List<RtpEndUserState> STATES_SHOWING_PIP_PLACEHOLDER = Arrays.asList(
|
|
||||||
RtpEndUserState.ACCEPTING_CALL,
|
RtpEndUserState.ACCEPTING_CALL,
|
||||||
RtpEndUserState.CONNECTING,
|
RtpEndUserState.CONNECTING,
|
||||||
RtpEndUserState.RECONNECTING
|
RtpEndUserState.RECONNECTING);
|
||||||
);
|
|
||||||
private static final String PROXIMITY_WAKE_LOCK_TAG = "conversations:in-rtp-session";
|
private static final String PROXIMITY_WAKE_LOCK_TAG = "conversations:in-rtp-session";
|
||||||
private static final int REQUEST_ACCEPT_CALL = 0x1111;
|
private static final int REQUEST_ACCEPT_CALL = 0x1111;
|
||||||
private WeakReference<JingleRtpConnection> rtpConnectionReference;
|
private WeakReference<JingleRtpConnection> rtpConnectionReference;
|
||||||
|
@ -121,7 +118,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
private PowerManager.WakeLock mProximityWakeLock;
|
private PowerManager.WakeLock mProximityWakeLock;
|
||||||
|
|
||||||
private final Handler mHandler = new Handler();
|
private final Handler mHandler = new Handler();
|
||||||
private final Runnable mTickExecutor = new Runnable() {
|
private final Runnable mTickExecutor =
|
||||||
|
new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
updateCallDuration();
|
updateCallDuration();
|
||||||
|
@ -137,18 +135,24 @@ 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 {
|
try {
|
||||||
videoTrack.addSink(surfaceViewRenderer);
|
videoTrack.addSink(surfaceViewRenderer);
|
||||||
} catch (final IllegalStateException e) {
|
} 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
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
getWindow()
|
||||||
|
.addFlags(
|
||||||
|
WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
|
||||||
| WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
|
| WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
|
||||||
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
|
| WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
|
||||||
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
|
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
|
||||||
|
@ -194,7 +198,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
return STATES_SHOWING_HELP_BUTTON.contains(requireRtpConnection().getEndUserState());
|
return STATES_SHOWING_HELP_BUTTON.contains(requireRtpConnection().getEndUserState());
|
||||||
} catch (IllegalStateException e) {
|
} catch (IllegalStateException e) {
|
||||||
final Intent intent = getIntent();
|
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) {
|
if (state != null) {
|
||||||
return STATES_SHOWING_HELP_BUTTON.contains(RtpEndUserState.valueOf(state));
|
return STATES_SHOWING_HELP_BUTTON.contains(RtpEndUserState.valueOf(state));
|
||||||
} else {
|
} else {
|
||||||
|
@ -204,8 +209,10 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isSwitchToConversationVisible() {
|
private boolean isSwitchToConversationVisible() {
|
||||||
final JingleRtpConnection connection = this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
|
final JingleRtpConnection connection =
|
||||||
return connection != null && STATES_SHOWING_SWITCH_TO_CHAT.contains(connection.getEndUserState());
|
this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
|
||||||
|
return connection != null
|
||||||
|
&& STATES_SHOWING_SWITCH_TO_CHAT.contains(connection.getEndUserState());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAudioOnlyConversation() {
|
private boolean isAudioOnlyConversation() {
|
||||||
|
@ -217,7 +224,9 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
|
|
||||||
private void switchToConversation() {
|
private void switchToConversation() {
|
||||||
final Contact contact = getWith();
|
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);
|
switchToConversation(conversation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +259,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
try {
|
try {
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
} catch (final ActivityNotFoundException e) {
|
} 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 Account account = extractAccount(intent);
|
||||||
final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH));
|
final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH));
|
||||||
final String state = intent.getStringExtra(EXTRA_LAST_REPORTED_STATE);
|
final String state = intent.getStringExtra(EXTRA_LAST_REPORTED_STATE);
|
||||||
if (!Intent.ACTION_VIEW.equals(action) || state == null || !END_CARD.contains(RtpEndUserState.valueOf(state))) {
|
if (!Intent.ACTION_VIEW.equals(action)
|
||||||
resetIntent(account, with, RtpEndUserState.RETRACTED, actionToMedia(intent.getAction()));
|
|| 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) {
|
private void rejectCall(View view) {
|
||||||
|
@ -291,7 +306,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
private void requestPermissionsAndAcceptCall() {
|
private void requestPermissionsAndAcceptCall() {
|
||||||
final List<String> permissions;
|
final List<String> permissions;
|
||||||
if (getMedia().contains(Media.VIDEO)) {
|
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 {
|
} else {
|
||||||
permissions = ImmutableList.of(Manifest.permission.RECORD_AUDIO);
|
permissions = ImmutableList.of(Manifest.permission.RECORD_AUDIO);
|
||||||
}
|
}
|
||||||
|
@ -302,7 +318,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkRecorderAndAcceptCall() {
|
private void checkRecorderAndAcceptCall() {
|
||||||
checkMicrophoneAvailability();
|
checkMicrophoneAvailabilityAsync();
|
||||||
try {
|
try {
|
||||||
requireRtpConnection().acceptCall();
|
requireRtpConnection().acceptCall();
|
||||||
} catch (final IllegalStateException e) {
|
} catch (final IllegalStateException e) {
|
||||||
|
@ -310,8 +326,11 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkMicrophoneAvailabilityAsync() {
|
||||||
|
new Thread(this::checkMicrophoneAvailability).start();
|
||||||
|
}
|
||||||
|
|
||||||
private void checkMicrophoneAvailability() {
|
private void checkMicrophoneAvailability() {
|
||||||
new Thread(() -> {
|
|
||||||
final long start = SystemClock.elapsedRealtime();
|
final long start = SystemClock.elapsedRealtime();
|
||||||
final boolean isMicrophoneAvailable = AppRTCAudioManager.isMicrophoneAvailable();
|
final boolean isMicrophoneAvailable = AppRTCAudioManager.isMicrophoneAvailable();
|
||||||
final long stop = SystemClock.elapsedRealtime();
|
final long stop = SystemClock.elapsedRealtime();
|
||||||
|
@ -319,9 +338,10 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
if (isMicrophoneAvailable) {
|
if (isMicrophoneAvailable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
runOnUiThread(() -> Toast.makeText(this, R.string.microphone_unavailable, Toast.LENGTH_LONG).show());
|
runOnUiThread(
|
||||||
}
|
() ->
|
||||||
).start();
|
Toast.makeText(this, R.string.microphone_unavailable, Toast.LENGTH_LONG)
|
||||||
|
.show());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void putScreenInCallMode() {
|
private void putScreenInCallMode() {
|
||||||
|
@ -331,9 +351,13 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
private void putScreenInCallMode(final Set<Media> media) {
|
private void putScreenInCallMode(final Set<Media> media) {
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
if (!media.contains(Media.VIDEO)) {
|
if (!media.contains(Media.VIDEO)) {
|
||||||
final JingleRtpConnection rtpConnection = rtpConnectionReference != null ? rtpConnectionReference.get() : null;
|
final JingleRtpConnection rtpConnection =
|
||||||
final AppRTCAudioManager audioManager = rtpConnection == null ? null : rtpConnection.getAudioManager();
|
rtpConnectionReference != null ? rtpConnectionReference.get() : null;
|
||||||
if (audioManager == null || audioManager.getSelectedAudioDevice() == AppRTCAudioManager.AudioDevice.EARPIECE) {
|
final AppRTCAudioManager audioManager =
|
||||||
|
rtpConnection == null ? null : rtpConnection.getAudioManager();
|
||||||
|
if (audioManager == null
|
||||||
|
|| audioManager.getSelectedAudioDevice()
|
||||||
|
== AppRTCAudioManager.AudioDevice.EARPIECE) {
|
||||||
acquireProximityWakeLock();
|
acquireProximityWakeLock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -346,30 +370,31 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
Log.e(Config.LOGTAG, "power manager not available");
|
Log.e(Config.LOGTAG, "power manager not available");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (isFinishing()) {
|
||||||
|
Log.e(Config.LOGTAG, "do not acquire wakelock. activity is finishing");
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (this.mProximityWakeLock == null) {
|
if (this.mProximityWakeLock == null) {
|
||||||
this.mProximityWakeLock = powerManager.newWakeLock(PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, PROXIMITY_WAKE_LOCK_TAG);
|
this.mProximityWakeLock =
|
||||||
|
powerManager.newWakeLock(
|
||||||
|
PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, PROXIMITY_WAKE_LOCK_TAG);
|
||||||
}
|
}
|
||||||
if (!this.mProximityWakeLock.isHeld()) {
|
if (!this.mProximityWakeLock.isHeld()) {
|
||||||
Log.d(Config.LOGTAG, "acquiring proximity wake lock");
|
Log.d(Config.LOGTAG, "acquiring proximity wake lock");
|
||||||
this.mProximityWakeLock.acquire();
|
this.mProximityWakeLock.acquire();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void releaseProximityWakeLock() {
|
private void releaseProximityWakeLock() {
|
||||||
if (this.mProximityWakeLock != null && mProximityWakeLock.isHeld()) {
|
if (this.mProximityWakeLock != null && mProximityWakeLock.isHeld()) {
|
||||||
Log.d(Config.LOGTAG, "releasing proximity wake lock");
|
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);
|
this.mProximityWakeLock.release(PowerManager.RELEASE_FLAG_WAIT_FOR_NO_PROXIMITY);
|
||||||
} else {
|
|
||||||
this.mProximityWakeLock.release();
|
|
||||||
}
|
|
||||||
this.mProximityWakeLock = null;
|
this.mProximityWakeLock = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void putProximityWakeLockInProperState(final AppRTCAudioManager.AudioDevice audioDevice) {
|
private void putProximityWakeLockInProperState(
|
||||||
|
final AppRTCAudioManager.AudioDevice audioDevice) {
|
||||||
if (audioDevice == AppRTCAudioManager.AudioDevice.EARPIECE) {
|
if (audioDevice == AppRTCAudioManager.AudioDevice.EARPIECE) {
|
||||||
acquireProximityWakeLock();
|
acquireProximityWakeLock();
|
||||||
} else {
|
} else {
|
||||||
|
@ -378,9 +403,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void refreshUiReal() {
|
protected void refreshUiReal() {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNewIntent(final Intent intent) {
|
public void onNewIntent(final Intent intent) {
|
||||||
|
@ -388,7 +411,9 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
super.onNewIntent(intent);
|
super.onNewIntent(intent);
|
||||||
setIntent(intent);
|
setIntent(intent);
|
||||||
if (xmppConnectionService == null) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
final Account account = extractAccount(intent);
|
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)) {
|
} else if (asList(ACTION_MAKE_VIDEO_CALL, ACTION_MAKE_VOICE_CALL).contains(action)) {
|
||||||
proposeJingleRtpSession(account, with, actionToMedia(action));
|
proposeJingleRtpSession(account, with, actionToMedia(action));
|
||||||
binding.with.setText(account.getRoster().getContact(with).getDisplayName());
|
setWith(account.getRoster().getContact(with));
|
||||||
binding.withJid.setText(with.asBareJid());
|
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException("received onNewIntent without sessionId");
|
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)) {
|
} else if (asList(ACTION_MAKE_VIDEO_CALL, ACTION_MAKE_VOICE_CALL).contains(action)) {
|
||||||
proposeJingleRtpSession(account, with, actionToMedia(action));
|
proposeJingleRtpSession(account, with, actionToMedia(action));
|
||||||
binding.with.setText(account.getRoster().getContact(with).getDisplayName());
|
setWith(account.getRoster().getContact(with));
|
||||||
binding.withJid.setText(with.asBareJid());
|
|
||||||
} else if (Intent.ACTION_VIEW.equals(action)) {
|
} else if (Intent.ACTION_VIEW.equals(action)) {
|
||||||
final String extraLastState = intent.getStringExtra(EXTRA_LAST_REPORTED_STATE);
|
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) {
|
if (state != null) {
|
||||||
Log.d(Config.LOGTAG, "restored last state from intent extra");
|
Log.d(Config.LOGTAG, "restored last state from intent extra");
|
||||||
updateButtonConfiguration(state);
|
updateButtonConfiguration(state);
|
||||||
updateVerifiedShield(false);
|
updateVerifiedShield(false);
|
||||||
updateStateDisplay(state);
|
updateStateDisplay(state);
|
||||||
updateProfilePicture(state);
|
updateIncomingCallScreen(state);
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
}
|
}
|
||||||
binding.with.setText(account.getRoster().getContact(with).getDisplayName());
|
setWith(account.getRoster().getContact(with));
|
||||||
binding.withJid.setText(with.asBareJid());
|
if (xmppConnectionService
|
||||||
if (xmppConnectionService.getJingleConnectionManager().fireJingleRtpConnectionStateUpdates()) {
|
.getJingleConnectionManager()
|
||||||
|
.fireJingleRtpConnectionStateUpdates()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (END_CARD.contains(state) || xmppConnectionService.getJingleConnectionManager().hasMatchingProposal(account, with)) {
|
if (END_CARD.contains(state)
|
||||||
|
|| xmppConnectionService
|
||||||
|
.getJingleConnectionManager()
|
||||||
|
.hasMatchingProposal(account, with)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Log.d(Config.LOGTAG, "restored state (" + state + ") was not an end card. finishing");
|
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) {
|
private void setWith() {
|
||||||
checkMicrophoneAvailability();
|
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()) {
|
if (with.isBareJid()) {
|
||||||
xmppConnectionService.getJingleConnectionManager().proposeJingleRtpSession(account, with, media);
|
xmppConnectionService
|
||||||
|
.getJingleConnectionManager()
|
||||||
|
.proposeJingleRtpSession(account, with, media);
|
||||||
} else {
|
} else {
|
||||||
final String sessionId = xmppConnectionService.getJingleConnectionManager().initializeRtpSession(account, with, media);
|
final String sessionId =
|
||||||
|
xmppConnectionService
|
||||||
|
.getJingleConnectionManager()
|
||||||
|
.initializeRtpSession(account, with, media);
|
||||||
initializeActivityWithRunningRtpSession(account, with, sessionId);
|
initializeActivityWithRunningRtpSession(account, with, sessionId);
|
||||||
resetIntent(account, with, sessionId);
|
resetIntent(account, with, sessionId);
|
||||||
}
|
}
|
||||||
|
@ -471,7 +514,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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);
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
if (PermissionUtils.allGranted(grantResults)) {
|
if (PermissionUtils.allGranted(grantResults)) {
|
||||||
if (requestCode == REQUEST_ACCEPT_CALL) {
|
if (requestCode == REQUEST_ACCEPT_CALL) {
|
||||||
|
@ -487,7 +531,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalStateException("Invalid permission result request");
|
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.remoteVideo.setOnAspectRatioChanged(null);
|
||||||
binding.localVideo.release();
|
binding.localVideo.release();
|
||||||
final WeakReference<JingleRtpConnection> weakReference = this.rtpConnectionReference;
|
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) {
|
if (jingleRtpConnection != null) {
|
||||||
releaseVideoTracks(jingleRtpConnection);
|
releaseVideoTracks(jingleRtpConnection);
|
||||||
}
|
}
|
||||||
|
@ -542,15 +588,18 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
if (switchToPictureInPicture()) {
|
if (switchToPictureInPicture()) {
|
||||||
return;
|
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) {
|
if (emptyReference(rtpConnectionReference) && xmppConnectionService != null) {
|
||||||
retractSessionProposal();
|
retractSessionProposal();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isConnected() {
|
private boolean isConnected() {
|
||||||
final JingleRtpConnection connection = this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
|
final JingleRtpConnection connection =
|
||||||
return connection != null && STATES_CONSIDERED_CONNECTED.contains(connection.getEndUserState());
|
this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
|
||||||
|
return connection != null
|
||||||
|
&& STATES_CONSIDERED_CONNECTED.contains(connection.getEndUserState());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean switchToPictureInPicture() {
|
private boolean switchToPictureInPicture() {
|
||||||
|
@ -568,12 +617,11 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
try {
|
try {
|
||||||
final Rational rational = this.binding.remoteVideo.getAspectRatio();
|
final Rational rational = this.binding.remoteVideo.getAspectRatio();
|
||||||
final Rational clippedRational = Rationals.clip(rational);
|
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(
|
enterPictureInPictureMode(
|
||||||
new PictureInPictureParams.Builder()
|
new PictureInPictureParams.Builder().setAspectRatio(clippedRational).build());
|
||||||
.setAspectRatio(clippedRational)
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
} catch (final IllegalStateException e) {
|
} 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);
|
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) {
|
public void onAspectRatioChanged(final Rational rational) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isPictureInPicture()) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isPictureInPicture()) {
|
||||||
final Rational clippedRational = Rationals.clip(rational);
|
final Rational clippedRational = Rationals.clip(rational);
|
||||||
Log.d(Config.LOGTAG, "suggested rational after aspect ratio change " + rational + ". clipped to " + clippedRational);
|
Log.d(
|
||||||
setPictureInPictureParams(new PictureInPictureParams.Builder()
|
Config.LOGTAG,
|
||||||
.setAspectRatio(clippedRational)
|
"suggested rational after aspect ratio change "
|
||||||
.build());
|
+ 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() {
|
private boolean shouldBePictureInPicture() {
|
||||||
try {
|
try {
|
||||||
final JingleRtpConnection rtpConnection = requireRtpConnection();
|
final JingleRtpConnection rtpConnection = requireRtpConnection();
|
||||||
return rtpConnection.getMedia().contains(Media.VIDEO) && Arrays.asList(
|
return rtpConnection.getMedia().contains(Media.VIDEO)
|
||||||
|
&& Arrays.asList(
|
||||||
RtpEndUserState.ACCEPTING_CALL,
|
RtpEndUserState.ACCEPTING_CALL,
|
||||||
RtpEndUserState.CONNECTING,
|
RtpEndUserState.CONNECTING,
|
||||||
RtpEndUserState.CONNECTED
|
RtpEndUserState.CONNECTED)
|
||||||
).contains(rtpConnection.getEndUserState());
|
.contains(rtpConnection.getEndUserState());
|
||||||
} catch (final IllegalStateException e) {
|
} catch (final IllegalStateException e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean initializeActivityWithRunningRtpSession(final Account account, Jid with, String sessionId) {
|
private boolean initializeActivityWithRunningRtpSession(
|
||||||
final WeakReference<JingleRtpConnection> reference = xmppConnectionService.getJingleConnectionManager()
|
final Account account, Jid with, String sessionId) {
|
||||||
|
final WeakReference<JingleRtpConnection> reference =
|
||||||
|
xmppConnectionService
|
||||||
|
.getJingleConnectionManager()
|
||||||
.findJingleRtpConnection(account, with, sessionId);
|
.findJingleRtpConnection(account, with, sessionId);
|
||||||
if (reference == null || reference.get() == null) {
|
if (reference == null || reference.get() == null) {
|
||||||
final JingleConnectionManager.TerminatedRtpSession terminatedRtpSession = xmppConnectionService
|
final JingleConnectionManager.TerminatedRtpSession terminatedRtpSession =
|
||||||
.getJingleConnectionManager().getTerminalSessionState(with, sessionId);
|
xmppConnectionService
|
||||||
|
.getJingleConnectionManager()
|
||||||
|
.getTerminalSessionState(with, sessionId);
|
||||||
if (terminatedRtpSession == null) {
|
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);
|
initializeWithTerminatedSessionState(account, with, terminatedRtpSession);
|
||||||
return true;
|
return true;
|
||||||
|
@ -628,7 +687,6 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
final RtpEndUserState currentState = requireRtpConnection().getEndUserState();
|
final RtpEndUserState currentState = requireRtpConnection().getEndUserState();
|
||||||
final boolean verified = requireRtpConnection().isVerified();
|
final boolean verified = requireRtpConnection().isVerified();
|
||||||
if (currentState == RtpEndUserState.ENDED) {
|
if (currentState == RtpEndUserState.ENDED) {
|
||||||
reference.get().throwStateTransitionException();
|
|
||||||
finish();
|
finish();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -636,21 +694,24 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
if (currentState == RtpEndUserState.INCOMING_CALL) {
|
if (currentState == RtpEndUserState.INCOMING_CALL) {
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
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();
|
putScreenInCallMode();
|
||||||
}
|
}
|
||||||
binding.with.setText(getWith().getDisplayName());
|
setWith();
|
||||||
binding.withJid.setText(with.asBareJid());
|
|
||||||
updateVideoViews(currentState);
|
updateVideoViews(currentState);
|
||||||
updateStateDisplay(currentState, media);
|
updateStateDisplay(currentState, media);
|
||||||
updateVerifiedShield(verified && STATES_SHOWING_SWITCH_TO_CHAT.contains(currentState));
|
updateVerifiedShield(verified && STATES_SHOWING_SWITCH_TO_CHAT.contains(currentState));
|
||||||
updateButtonConfiguration(currentState, media);
|
updateButtonConfiguration(currentState, media);
|
||||||
updateProfilePicture(currentState);
|
updateIncomingCallScreen(currentState);
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
return false;
|
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()");
|
Log.d(Config.LOGTAG, "initializeWithTerminatedSessionState()");
|
||||||
if (terminatedRtpSession.state == RtpEndUserState.ENDED) {
|
if (terminatedRtpSession.state == RtpEndUserState.ENDED) {
|
||||||
finish();
|
finish();
|
||||||
|
@ -660,15 +721,15 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
resetIntent(account, with, terminatedRtpSession.state, terminatedRtpSession.media);
|
resetIntent(account, with, terminatedRtpSession.state, terminatedRtpSession.media);
|
||||||
updateButtonConfiguration(state);
|
updateButtonConfiguration(state);
|
||||||
updateStateDisplay(state);
|
updateStateDisplay(state);
|
||||||
updateProfilePicture(state);
|
updateIncomingCallScreen(state);
|
||||||
updateCallDuration();
|
updateCallDuration();
|
||||||
updateVerifiedShield(false);
|
updateVerifiedShield(false);
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
binding.with.setText(account.getRoster().getContact(with).getDisplayName());
|
setWith(account.getRoster().getContact(with));
|
||||||
binding.withJid.setText(with.asBareJid());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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));
|
runOnUiThread(() -> initializeActivityWithRunningRtpSession(account, with, sessionId));
|
||||||
resetIntent(account, with, sessionId);
|
resetIntent(account, with, sessionId);
|
||||||
}
|
}
|
||||||
|
@ -745,9 +806,11 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
setTitle(R.string.rtp_state_security_error);
|
setTitle(R.string.rtp_state_security_error);
|
||||||
break;
|
break;
|
||||||
case ENDED:
|
case ENDED:
|
||||||
throw new IllegalStateException("Activity should have called finishAndReleaseWakeLock();");
|
throw new IllegalStateException(
|
||||||
|
"Activity should have called finishAndReleaseWakeLock();");
|
||||||
default:
|
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);
|
this.binding.verified.setVisibility(verified ? View.VISIBLE : View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateProfilePicture(final RtpEndUserState state) {
|
private void updateIncomingCallScreen(final RtpEndUserState state) {
|
||||||
updateProfilePicture(state, null);
|
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) {
|
if (state == RtpEndUserState.INCOMING_CALL || state == RtpEndUserState.ACCEPTING_CALL) {
|
||||||
final boolean show = getResources().getBoolean(R.bool.show_avatar_incoming_call);
|
final boolean show = getResources().getBoolean(R.bool.show_avatar_incoming_call);
|
||||||
if (show) {
|
if (show) {
|
||||||
binding.contactPhoto.setVisibility(View.VISIBLE);
|
binding.contactPhoto.setVisibility(View.VISIBLE);
|
||||||
if (contact == null) {
|
if (contact == null) {
|
||||||
AvatarWorkerTask.loadAvatar(getWith(), binding.contactPhoto, R.dimen.publish_avatar_size);
|
AvatarWorkerTask.loadAvatar(
|
||||||
|
getWith(), binding.contactPhoto, R.dimen.publish_avatar_size);
|
||||||
} else {
|
} else {
|
||||||
AvatarWorkerTask.loadAvatar(contact, binding.contactPhoto, R.dimen.publish_avatar_size);
|
AvatarWorkerTask.loadAvatar(
|
||||||
|
contact, binding.contactPhoto, R.dimen.publish_avatar_size);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.contactPhoto.setVisibility(View.GONE);
|
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 {
|
} else {
|
||||||
|
binding.usingAccount.setVisibility(View.GONE);
|
||||||
binding.contactPhoto.setVisibility(View.GONE);
|
binding.contactPhoto.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -820,8 +892,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
RtpEndUserState.CONNECTIVITY_LOST_ERROR,
|
RtpEndUserState.CONNECTIVITY_LOST_ERROR,
|
||||||
RtpEndUserState.APPLICATION_ERROR,
|
RtpEndUserState.APPLICATION_ERROR,
|
||||||
RtpEndUserState.RETRACTED,
|
RtpEndUserState.RETRACTED,
|
||||||
RtpEndUserState.SECURITY_ERROR
|
RtpEndUserState.SECURITY_ERROR)
|
||||||
).contains(state)) {
|
.contains(state)) {
|
||||||
this.binding.rejectCall.setContentDescription(getString(R.string.exit));
|
this.binding.rejectCall.setContentDescription(getString(R.string.exit));
|
||||||
this.binding.rejectCall.setOnClickListener(this::exit);
|
this.binding.rejectCall.setOnClickListener(this::exit);
|
||||||
this.binding.rejectCall.setImageResource(R.drawable.ic_clear_white_48dp);
|
this.binding.rejectCall.setImageResource(R.drawable.ic_clear_white_48dp);
|
||||||
|
@ -851,26 +923,29 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateInCallButtonConfiguration() {
|
private void updateInCallButtonConfiguration() {
|
||||||
updateInCallButtonConfiguration(requireRtpConnection().getEndUserState(), requireRtpConnection().getMedia());
|
updateInCallButtonConfiguration(
|
||||||
|
requireRtpConnection().getEndUserState(), requireRtpConnection().getMedia());
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
@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()) {
|
if (STATES_CONSIDERED_CONNECTED.contains(state) && !isPictureInPicture()) {
|
||||||
Preconditions.checkArgument(media.size() > 0, "Media must not be empty");
|
Preconditions.checkArgument(media.size() > 0, "Media must not be empty");
|
||||||
if (media.contains(Media.VIDEO)) {
|
if (media.contains(Media.VIDEO)) {
|
||||||
final JingleRtpConnection rtpConnection = requireRtpConnection();
|
final JingleRtpConnection rtpConnection = requireRtpConnection();
|
||||||
updateInCallButtonConfigurationVideo(rtpConnection.isVideoEnabled(), rtpConnection.isCameraSwitchable());
|
updateInCallButtonConfigurationVideo(
|
||||||
|
rtpConnection.isVideoEnabled(), rtpConnection.isCameraSwitchable());
|
||||||
} else {
|
} else {
|
||||||
final AppRTCAudioManager audioManager = requireRtpConnection().getAudioManager();
|
final AppRTCAudioManager audioManager = requireRtpConnection().getAudioManager();
|
||||||
updateInCallButtonConfigurationSpeaker(
|
updateInCallButtonConfigurationSpeaker(
|
||||||
audioManager.getSelectedAudioDevice(),
|
audioManager.getSelectedAudioDevice(),
|
||||||
audioManager.getAudioDevices().size()
|
audioManager.getAudioDevices().size());
|
||||||
);
|
|
||||||
this.binding.inCallActionFarRight.setVisibility(View.GONE);
|
this.binding.inCallActionFarRight.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
if (media.contains(Media.AUDIO)) {
|
if (media.contains(Media.AUDIO)) {
|
||||||
updateInCallButtonConfigurationMicrophone(requireRtpConnection().isMicrophoneEnabled());
|
updateInCallButtonConfigurationMicrophone(
|
||||||
|
requireRtpConnection().isMicrophoneEnabled());
|
||||||
} else {
|
} else {
|
||||||
this.binding.inCallActionLeft.setVisibility(View.GONE);
|
this.binding.inCallActionLeft.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
@ -882,10 +957,12 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
private void updateInCallButtonConfigurationSpeaker(final AppRTCAudioManager.AudioDevice selectedAudioDevice, final int numberOfChoices) {
|
private void updateInCallButtonConfigurationSpeaker(
|
||||||
|
final AppRTCAudioManager.AudioDevice selectedAudioDevice, final int numberOfChoices) {
|
||||||
switch (selectedAudioDevice) {
|
switch (selectedAudioDevice) {
|
||||||
case EARPIECE:
|
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) {
|
if (numberOfChoices >= 2) {
|
||||||
this.binding.inCallActionRight.setOnClickListener(this::switchToSpeaker);
|
this.binding.inCallActionRight.setOnClickListener(this::switchToSpeaker);
|
||||||
} else {
|
} else {
|
||||||
|
@ -908,7 +985,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case BLUETOOTH:
|
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.setOnClickListener(null);
|
||||||
this.binding.inCallActionRight.setClickable(false);
|
this.binding.inCallActionRight.setClickable(false);
|
||||||
break;
|
break;
|
||||||
|
@ -917,10 +995,12 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
@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);
|
this.binding.inCallActionRight.setVisibility(View.VISIBLE);
|
||||||
if (isCameraSwitchable) {
|
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.setVisibility(View.VISIBLE);
|
||||||
this.binding.inCallActionFarRight.setOnClickListener(this::switchCamera);
|
this.binding.inCallActionFarRight.setOnClickListener(this::switchCamera);
|
||||||
} else {
|
} else {
|
||||||
|
@ -936,7 +1016,9 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
}
|
}
|
||||||
|
|
||||||
private void switchCamera(final View view) {
|
private void switchCamera(final View view) {
|
||||||
Futures.addCallback(requireRtpConnection().switchCamera(), new FutureCallback<Boolean>() {
|
Futures.addCallback(
|
||||||
|
requireRtpConnection().switchCamera(),
|
||||||
|
new FutureCallback<Boolean>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(@NullableDecl Boolean isFrontCamera) {
|
public void onSuccess(@NullableDecl Boolean isFrontCamera) {
|
||||||
binding.localVideo.setMirror(isFrontCamera);
|
binding.localVideo.setMirror(isFrontCamera);
|
||||||
|
@ -944,10 +1026,18 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(@NonNull final Throwable throwable) {
|
public void onFailure(@NonNull final Throwable throwable) {
|
||||||
Log.d(Config.LOGTAG, "could not switch camera", Throwables.getRootCause(throwable));
|
Log.d(
|
||||||
Toast.makeText(RtpSessionActivity.this, R.string.could_not_switch_camera, Toast.LENGTH_LONG).show();
|
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());
|
},
|
||||||
|
MainThreadExecutor.getInstance());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void enableVideo(View view) {
|
private void enableVideo(View view) {
|
||||||
|
@ -963,7 +1053,6 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
private void disableVideo(View view) {
|
private void disableVideo(View view) {
|
||||||
requireRtpConnection().setVideoEnabled(false);
|
requireRtpConnection().setVideoEnabled(false);
|
||||||
updateInCallButtonConfigurationVideo(false, requireRtpConnection().isCameraSwitchable());
|
updateInCallButtonConfigurationVideo(false, requireRtpConnection().isCameraSwitchable());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
|
@ -979,7 +1068,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateCallDuration() {
|
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)) {
|
if (connection == null || connection.getMedia().contains(Media.VIDEO)) {
|
||||||
this.binding.duration.setVisibility(View.GONE);
|
this.binding.duration.setVisibility(View.GONE);
|
||||||
return;
|
return;
|
||||||
|
@ -987,7 +1077,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
if (connection.zeroDuration()) {
|
if (connection.zeroDuration()) {
|
||||||
this.binding.duration.setVisibility(View.GONE);
|
this.binding.duration.setVisibility(View.GONE);
|
||||||
} else {
|
} 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);
|
this.binding.duration.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1046,8 +1137,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
addSink(remoteVideoTrack.get(), binding.remoteVideo);
|
addSink(remoteVideoTrack.get(), binding.remoteVideo);
|
||||||
binding.remoteVideo.setScalingType(
|
binding.remoteVideo.setScalingType(
|
||||||
RendererCommon.ScalingType.SCALE_ASPECT_FILL,
|
RendererCommon.ScalingType.SCALE_ASPECT_FILL,
|
||||||
RendererCommon.ScalingType.SCALE_ASPECT_FIT
|
RendererCommon.ScalingType.SCALE_ASPECT_FIT);
|
||||||
);
|
|
||||||
if (state == RtpEndUserState.CONNECTED) {
|
if (state == RtpEndUserState.CONNECTED) {
|
||||||
binding.appBarLayout.setVisibility(View.GONE);
|
binding.appBarLayout.setVisibility(View.GONE);
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN);
|
||||||
|
@ -1070,7 +1160,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<VideoTrack> getLocalVideoTrack() {
|
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) {
|
if (connection == null) {
|
||||||
return Optional.absent();
|
return Optional.absent();
|
||||||
}
|
}
|
||||||
|
@ -1078,7 +1169,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<VideoTrack> getRemoteVideoTrack() {
|
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) {
|
if (connection == null) {
|
||||||
return Optional.absent();
|
return Optional.absent();
|
||||||
}
|
}
|
||||||
|
@ -1100,12 +1192,16 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
}
|
}
|
||||||
|
|
||||||
private void switchToEarpiece(View view) {
|
private void switchToEarpiece(View view) {
|
||||||
requireRtpConnection().getAudioManager().setDefaultAudioDevice(AppRTCAudioManager.AudioDevice.EARPIECE);
|
requireRtpConnection()
|
||||||
|
.getAudioManager()
|
||||||
|
.setDefaultAudioDevice(AppRTCAudioManager.AudioDevice.EARPIECE);
|
||||||
acquireProximityWakeLock();
|
acquireProximityWakeLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void switchToSpeaker(View view) {
|
private void switchToSpeaker(View view) {
|
||||||
requireRtpConnection().getAudioManager().setDefaultAudioDevice(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE);
|
requireRtpConnection()
|
||||||
|
.getAudioManager()
|
||||||
|
.setDefaultAudioDevice(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE);
|
||||||
releaseProximityWakeLock();
|
releaseProximityWakeLock();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1129,12 +1225,15 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
final Intent intent = getIntent();
|
final Intent intent = getIntent();
|
||||||
final Account account = extractAccount(intent);
|
final Account account = extractAccount(intent);
|
||||||
final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH));
|
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);
|
final Intent launchIntent = new Intent(this, ConversationsActivity.class);
|
||||||
launchIntent.setAction(ConversationsActivity.ACTION_VIEW_CONVERSATION);
|
launchIntent.setAction(ConversationsActivity.ACTION_VIEW_CONVERSATION);
|
||||||
launchIntent.putExtra(ConversationsActivity.EXTRA_CONVERSATION, conversation.getUuid());
|
launchIntent.putExtra(ConversationsActivity.EXTRA_CONVERSATION, conversation.getUuid());
|
||||||
launchIntent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
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);
|
startActivity(launchIntent);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
@ -1146,7 +1245,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
}
|
}
|
||||||
|
|
||||||
private JingleRtpConnection requireRtpConnection() {
|
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) {
|
if (connection == null) {
|
||||||
throw new IllegalStateException("No RTP connection found");
|
throw new IllegalStateException("No RTP connection found");
|
||||||
}
|
}
|
||||||
|
@ -1154,12 +1254,14 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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 + ")");
|
Log.d(Config.LOGTAG, "onJingleRtpConnectionUpdate(" + state + ")");
|
||||||
if (END_CARD.contains(state)) {
|
if (END_CARD.contains(state)) {
|
||||||
Log.d(Config.LOGTAG, "end card reached");
|
Log.d(Config.LOGTAG, "end card reached");
|
||||||
releaseProximityWakeLock();
|
releaseProximityWakeLock();
|
||||||
runOnUiThread(() -> getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON));
|
runOnUiThread(
|
||||||
|
() -> getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON));
|
||||||
}
|
}
|
||||||
if (with.isBareJid()) {
|
if (with.isBareJid()) {
|
||||||
updateRtpSessionProposalState(account, with, state);
|
updateRtpSessionProposalState(account, with, state);
|
||||||
|
@ -1183,12 +1285,14 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
finish();
|
finish();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
runOnUiThread(() -> {
|
runOnUiThread(
|
||||||
|
() -> {
|
||||||
updateStateDisplay(state, media);
|
updateStateDisplay(state, media);
|
||||||
updateVerifiedShield(verified && STATES_SHOWING_SWITCH_TO_CHAT.contains(state));
|
updateVerifiedShield(
|
||||||
|
verified && STATES_SHOWING_SWITCH_TO_CHAT.contains(state));
|
||||||
updateButtonConfiguration(state, media);
|
updateButtonConfiguration(state, media);
|
||||||
updateVideoViews(state);
|
updateVideoViews(state);
|
||||||
updateProfilePicture(state, contact);
|
updateIncomingCallScreen(state, contact);
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
});
|
});
|
||||||
if (END_CARD.contains(state)) {
|
if (END_CARD.contains(state)) {
|
||||||
|
@ -1203,8 +1307,15 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAudioDeviceChanged(AppRTCAudioManager.AudioDevice selectedAudioDevice, Set<AppRTCAudioManager.AudioDevice> availableAudioDevices) {
|
public void onAudioDeviceChanged(
|
||||||
Log.d(Config.LOGTAG, "onAudioDeviceChanged in activity: selected:" + selectedAudioDevice + ", available:" + availableAudioDevices);
|
AppRTCAudioManager.AudioDevice selectedAudioDevice,
|
||||||
|
Set<AppRTCAudioManager.AudioDevice> availableAudioDevices) {
|
||||||
|
Log.d(
|
||||||
|
Config.LOGTAG,
|
||||||
|
"onAudioDeviceChanged in activity: selected:"
|
||||||
|
+ selectedAudioDevice
|
||||||
|
+ ", available:"
|
||||||
|
+ availableAudioDevices);
|
||||||
try {
|
try {
|
||||||
if (getMedia().contains(Media.VIDEO)) {
|
if (getMedia().contains(Media.VIDEO)) {
|
||||||
Log.d(Config.LOGTAG, "nothing to do; in video mode");
|
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();
|
final AppRTCAudioManager audioManager = requireRtpConnection().getAudioManager();
|
||||||
updateInCallButtonConfigurationSpeaker(
|
updateInCallButtonConfigurationSpeaker(
|
||||||
audioManager.getSelectedAudioDevice(),
|
audioManager.getSelectedAudioDevice(),
|
||||||
audioManager.getAudioDevices().size()
|
audioManager.getAudioDevices().size());
|
||||||
);
|
|
||||||
} else if (END_CARD.contains(endUserState)) {
|
} 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 {
|
} else {
|
||||||
putProximityWakeLockInProperState(selectedAudioDevice);
|
putProximityWakeLockInProperState(selectedAudioDevice);
|
||||||
}
|
}
|
||||||
|
@ -1233,18 +1345,21 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
outState.putBoolean("dialpad_visible", binding.dialpad.getVisibility() == View.VISIBLE);
|
outState.putBoolean("dialpad_visible", binding.dialpad.getVisibility() == View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateRtpSessionProposalState(final Account account, final Jid with, final RtpEndUserState state) {
|
private void updateRtpSessionProposalState(
|
||||||
|
final Account account, final Jid with, final RtpEndUserState state) {
|
||||||
final Intent currentIntent = getIntent();
|
final Intent currentIntent = getIntent();
|
||||||
final String withExtra = currentIntent == null ? null : currentIntent.getStringExtra(EXTRA_WITH);
|
final String withExtra =
|
||||||
|
currentIntent == null ? null : currentIntent.getStringExtra(EXTRA_WITH);
|
||||||
if (withExtra == null) {
|
if (withExtra == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Jid.ofEscaped(withExtra).asBareJid().equals(with)) {
|
if (Jid.ofEscaped(withExtra).asBareJid().equals(with)) {
|
||||||
runOnUiThread(() -> {
|
runOnUiThread(
|
||||||
|
() -> {
|
||||||
updateVerifiedShield(false);
|
updateVerifiedShield(false);
|
||||||
updateStateDisplay(state);
|
updateStateDisplay(state);
|
||||||
updateButtonConfiguration(state);
|
updateButtonConfiguration(state);
|
||||||
updateProfilePicture(state);
|
updateIncomingCallScreen(state);
|
||||||
invalidateOptionsMenu();
|
invalidateOptionsMenu();
|
||||||
});
|
});
|
||||||
resetIntent(account, with, state, actionToMedia(currentIntent.getAction()));
|
resetIntent(account, with, state, actionToMedia(currentIntent.getAction()));
|
||||||
|
@ -1257,16 +1372,22 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe
|
||||||
setIntent(intent);
|
setIntent(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void resetIntent(final Account account, Jid with, final RtpEndUserState state, final Set<Media> media) {
|
private void resetIntent(
|
||||||
|
final Account account, Jid with, final RtpEndUserState state, final Set<Media> media) {
|
||||||
final Intent intent = new Intent(Intent.ACTION_VIEW);
|
final Intent intent = new Intent(Intent.ACTION_VIEW);
|
||||||
intent.putExtra(EXTRA_ACCOUNT, account.getJid().toEscapedString());
|
intent.putExtra(EXTRA_ACCOUNT, account.getJid().toEscapedString());
|
||||||
if (account.getRoster().getContact(with).getPresences().anySupport(Namespace.JINGLE_MESSAGE)) {
|
if (account.getRoster()
|
||||||
|
.getContact(with)
|
||||||
|
.getPresences()
|
||||||
|
.anySupport(Namespace.JINGLE_MESSAGE)) {
|
||||||
intent.putExtra(EXTRA_WITH, with.asBareJid().toEscapedString());
|
intent.putExtra(EXTRA_WITH, with.asBareJid().toEscapedString());
|
||||||
} else {
|
} else {
|
||||||
intent.putExtra(EXTRA_WITH, with.toEscapedString());
|
intent.putExtra(EXTRA_WITH, with.toEscapedString());
|
||||||
}
|
}
|
||||||
intent.putExtra(EXTRA_LAST_REPORTED_STATE, state.toString());
|
intent.putExtra(EXTRA_LAST_REPORTED_STATE, state.toString());
|
||||||
intent.putExtra(EXTRA_LAST_ACTION, media.contains(Media.VIDEO) ? ACTION_MAKE_VIDEO_CALL : ACTION_MAKE_VOICE_CALL);
|
intent.putExtra(
|
||||||
|
EXTRA_LAST_ACTION,
|
||||||
|
media.contains(Media.VIDEO) ? ACTION_MAKE_VIDEO_CALL : ACTION_MAKE_VOICE_CALL);
|
||||||
setIntent(intent);
|
setIntent(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -224,7 +224,7 @@ public class SettingsActivity extends XmppActivity implements
|
||||||
|
|
||||||
final Preference createBackupPreference = mSettingsFragment.findPreference("create_backup");
|
final Preference createBackupPreference = mSettingsFragment.findPreference("create_backup");
|
||||||
if (createBackupPreference != null) {
|
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 -> {
|
createBackupPreference.setOnPreferenceClickListener(preference -> {
|
||||||
if (hasStoragePermission(REQUEST_CREATE_BACKUP)) {
|
if (hasStoragePermission(REQUEST_CREATE_BACKUP)) {
|
||||||
createBackup();
|
createBackup();
|
||||||
|
|
|
@ -71,10 +71,10 @@ import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.entities.Presences;
|
import eu.siacs.conversations.entities.Presences;
|
||||||
import eu.siacs.conversations.services.AvatarService;
|
import eu.siacs.conversations.services.AvatarService;
|
||||||
import eu.siacs.conversations.services.BarcodeProvider;
|
import eu.siacs.conversations.services.BarcodeProvider;
|
||||||
|
import eu.siacs.conversations.services.EmojiInitializationService;
|
||||||
import eu.siacs.conversations.services.QuickConversationsService;
|
import eu.siacs.conversations.services.QuickConversationsService;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
|
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.MenuDoubleTabUtil;
|
||||||
import eu.siacs.conversations.ui.util.PresenceSelector;
|
import eu.siacs.conversations.ui.util.PresenceSelector;
|
||||||
import eu.siacs.conversations.ui.util.SoftKeyboardUtils;
|
import eu.siacs.conversations.ui.util.SoftKeyboardUtils;
|
||||||
|
@ -408,7 +408,7 @@ public abstract class XmppActivity extends ActionBarActivity {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
metrics = getResources().getDisplayMetrics();
|
metrics = getResources().getDisplayMetrics();
|
||||||
ExceptionHelper.init(getApplicationContext());
|
ExceptionHelper.init(getApplicationContext());
|
||||||
new EmojiService(this).init();
|
EmojiInitializationService.execute(this);
|
||||||
this.isCameraFeatureAvailable = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
|
this.isCameraFeatureAvailable = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
|
||||||
this.mTheme = findTheme();
|
this.mTheme = findTheme();
|
||||||
setTheme(this.mTheme);
|
setTheme(this.mTheme);
|
||||||
|
|
|
@ -23,7 +23,6 @@ import eu.siacs.conversations.ui.ConversationFragment;
|
||||||
import eu.siacs.conversations.ui.XmppActivity;
|
import eu.siacs.conversations.ui.XmppActivity;
|
||||||
import eu.siacs.conversations.ui.util.AvatarWorkerTask;
|
import eu.siacs.conversations.ui.util.AvatarWorkerTask;
|
||||||
import eu.siacs.conversations.ui.util.StyledAttributes;
|
import eu.siacs.conversations.ui.util.StyledAttributes;
|
||||||
import eu.siacs.conversations.utils.EmojiWrapper;
|
|
||||||
import eu.siacs.conversations.utils.IrregularUnicodeDetector;
|
import eu.siacs.conversations.utils.IrregularUnicodeDetector;
|
||||||
import eu.siacs.conversations.utils.UIHelper;
|
import eu.siacs.conversations.utils.UIHelper;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
|
@ -57,7 +56,7 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
|
||||||
if (name instanceof Jid) {
|
if (name instanceof Jid) {
|
||||||
viewHolder.binding.conversationName.setText(IrregularUnicodeDetector.style(activity, (Jid) name));
|
viewHolder.binding.conversationName.setText(IrregularUnicodeDetector.style(activity, (Jid) name));
|
||||||
} else {
|
} else {
|
||||||
viewHolder.binding.conversationName.setText(EmojiWrapper.transform(name));
|
viewHolder.binding.conversationName.setText(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (conversation == ConversationFragment.getConversation(activity)) {
|
if (conversation == ConversationFragment.getConversation(activity)) {
|
||||||
|
@ -85,7 +84,7 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte
|
||||||
|
|
||||||
if (draft != null) {
|
if (draft != null) {
|
||||||
viewHolder.binding.conversationLastmsgImg.setVisibility(View.GONE);
|
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.setText(R.string.draft);
|
||||||
viewHolder.binding.senderName.setVisibility(View.VISIBLE);
|
viewHolder.binding.senderName.setVisibility(View.VISIBLE);
|
||||||
viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.NORMAL);
|
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());
|
final Pair<CharSequence, Boolean> preview = UIHelper.getMessagePreview(activity, message, viewHolder.binding.conversationLastmsg.getCurrentTextColor());
|
||||||
if (showPreviewText) {
|
if (showPreviewText) {
|
||||||
viewHolder.binding.conversationLastmsg.setText(EmojiWrapper.transform(UIHelper.shorten(preview.first)));
|
viewHolder.binding.conversationLastmsg.setText(UIHelper.shorten(preview.first));
|
||||||
} else {
|
} else {
|
||||||
viewHolder.binding.conversationLastmsgImg.setContentDescription(preview.first);
|
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.XmppActivity;
|
||||||
import eu.siacs.conversations.ui.util.AvatarWorkerTask;
|
import eu.siacs.conversations.ui.util.AvatarWorkerTask;
|
||||||
import eu.siacs.conversations.ui.util.StyledAttributes;
|
import eu.siacs.conversations.ui.util.StyledAttributes;
|
||||||
import eu.siacs.conversations.utils.EmojiWrapper;
|
|
||||||
import eu.siacs.conversations.utils.IrregularUnicodeDetector;
|
import eu.siacs.conversations.utils.IrregularUnicodeDetector;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
|
|
||||||
|
@ -85,7 +84,7 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
|
||||||
} else {
|
} else {
|
||||||
viewHolder.jid.setVisibility(View.GONE);
|
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);
|
AvatarWorkerTask.loadAvatar(item, viewHolder.avatar, R.dimen.avatar);
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ import eu.siacs.conversations.ui.util.ViewUtil;
|
||||||
|
|
||||||
public class MediaAdapter extends RecyclerView.Adapter<MediaAdapter.MediaViewHolder> {
|
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/pdf",
|
||||||
"application/vnd.oasis.opendocument.text",
|
"application/vnd.oasis.opendocument.text",
|
||||||
"application/msword",
|
"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.util.ViewUtil;
|
||||||
import eu.siacs.conversations.ui.widget.ClickableMovementMethod;
|
import eu.siacs.conversations.ui.widget.ClickableMovementMethod;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
import eu.siacs.conversations.utils.EmojiWrapper;
|
|
||||||
import eu.siacs.conversations.utils.Emoticons;
|
import eu.siacs.conversations.utils.Emoticons;
|
||||||
import eu.siacs.conversations.utils.GeoHelper;
|
import eu.siacs.conversations.utils.GeoHelper;
|
||||||
import eu.siacs.conversations.utils.MessageUtils;
|
import eu.siacs.conversations.utils.MessageUtils;
|
||||||
|
@ -339,7 +338,7 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
||||||
Spannable span = new SpannableString(body);
|
Spannable span = new SpannableString(body);
|
||||||
float size = Emoticons.isEmoji(body) ? 3.0f : 2.0f;
|
float size = Emoticons.isEmoji(body) ? 3.0f : 2.0f;
|
||||||
span.setSpan(new RelativeSizeSpan(size), 0, body.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
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) {
|
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.setAutoLinkMask(0);
|
||||||
viewHolder.messageBody.setText(EmojiWrapper.transform(body));
|
viewHolder.messageBody.setText(body);
|
||||||
viewHolder.messageBody.setMovementMethod(ClickableMovementMethod.getInstance());
|
viewHolder.messageBody.setMovementMethod(ClickableMovementMethod.getInstance());
|
||||||
} else {
|
} else {
|
||||||
viewHolder.messageBody.setText("");
|
viewHolder.messageBody.setText("");
|
||||||
|
|
|
@ -37,9 +37,10 @@ public class ViewUtil {
|
||||||
view(context, file, mime);
|
view(context, file, mime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void view(Context context, File file, String mime) {
|
private static void view(Context context, File file, String mime) {
|
||||||
Intent openIntent = new Intent(Intent.ACTION_VIEW);
|
Log.d(Config.LOGTAG,"viewing "+file.getAbsolutePath()+" "+mime);
|
||||||
Uri uri;
|
final Intent openIntent = new Intent(Intent.ACTION_VIEW);
|
||||||
|
final Uri uri;
|
||||||
try {
|
try {
|
||||||
uri = FileBackend.getUriForFile(context, file);
|
uri = FileBackend.getUriForFile(context, file);
|
||||||
} catch (SecurityException e) {
|
} catch (SecurityException e) {
|
||||||
|
@ -49,14 +50,9 @@ public class ViewUtil {
|
||||||
}
|
}
|
||||||
openIntent.setDataAndType(uri, mime);
|
openIntent.setDataAndType(uri, mime);
|
||||||
openIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
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 {
|
try {
|
||||||
context.startActivity(openIntent);
|
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();
|
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.EditorInfo;
|
||||||
import android.view.inputmethod.InputConnection;
|
import android.view.inputmethod.InputConnection;
|
||||||
|
|
||||||
|
import androidx.appcompat.widget.AppCompatEditText;
|
||||||
import androidx.core.view.inputmethod.EditorInfoCompat;
|
import androidx.core.view.inputmethod.EditorInfoCompat;
|
||||||
import androidx.core.view.inputmethod.InputConnectionCompat;
|
import androidx.core.view.inputmethod.InputConnectionCompat;
|
||||||
import androidx.core.view.inputmethod.InputContentInfoCompat;
|
import androidx.core.view.inputmethod.InputContentInfoCompat;
|
||||||
|
@ -26,7 +27,7 @@ import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.ui.util.QuoteHelper;
|
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 static final InputFilter SPAN_FILTER = (source, start, end, dest, dstart, dend) -> source instanceof Spanned ? source.toString() : source;
|
||||||
private final ExecutorService executor = Executors.newSingleThreadExecutor();
|
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}");
|
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};
|
final public static byte[] ONE = new byte[]{0, 0, 0, 1};
|
||||||
private static final char[] CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz123456789+-/#$!?".toCharArray();
|
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[] VOWELS = "aeiou".toCharArray();
|
||||||
private static final char[] CONSONANTS = "bcfghjklmnpqrstvwxyz".toCharArray();
|
private static final char[] CONSONANTS = "bcfghjklmnpqrstvwxyz".toCharArray();
|
||||||
private final static char[] hexArray = "0123456789abcdef".toCharArray();
|
private final static char[] hexArray = "0123456789abcdef".toCharArray();
|
||||||
|
|
|
@ -1,4 +1,14 @@
|
||||||
package eu.siacs.conversations.utils;
|
package eu.siacs.conversations.utils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
public class FileWriterException extends Exception {
|
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) {
|
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()) {
|
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;
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
return cursor.getString(index);
|
||||||
|
}
|
||||||
|
} catch (final Exception e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package eu.siacs.conversations.xmpp.jingle;
|
package eu.siacs.conversations.xmpp.jingle;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.google.common.base.MoreObjects;
|
import com.google.common.base.MoreObjects;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
@ -110,6 +112,7 @@ public abstract class AbstractJingleConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@NonNull
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return MoreObjects.toStringHelper(this)
|
return MoreObjects.toStringHelper(this)
|
||||||
.add("account", account.getJid())
|
.add("account", account.getJid())
|
||||||
|
|
|
@ -52,14 +52,16 @@ import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
||||||
|
|
||||||
public class JingleConnectionManager extends AbstractConnectionManager {
|
public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
static final ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE = Executors.newSingleThreadScheduledExecutor();
|
static final ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE =
|
||||||
|
Executors.newSingleThreadScheduledExecutor();
|
||||||
final ToneManager toneManager;
|
final ToneManager toneManager;
|
||||||
private final HashMap<RtpSessionProposal, DeviceDiscoveryState> rtpSessionProposals = new HashMap<>();
|
private final HashMap<RtpSessionProposal, DeviceDiscoveryState> rtpSessionProposals =
|
||||||
private final ConcurrentHashMap<AbstractJingleConnection.Id, AbstractJingleConnection> connections = new ConcurrentHashMap<>();
|
new HashMap<>();
|
||||||
|
private final ConcurrentHashMap<AbstractJingleConnection.Id, AbstractJingleConnection>
|
||||||
|
connections = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private final Cache<PersistableSessionId, TerminatedRtpSession> terminatedSessions = CacheBuilder.newBuilder()
|
private final Cache<PersistableSessionId, TerminatedRtpSession> terminatedSessions =
|
||||||
.expireAfterWrite(24, TimeUnit.HOURS)
|
CacheBuilder.newBuilder().expireAfterWrite(24, TimeUnit.HOURS).build();
|
||||||
.build();
|
|
||||||
|
|
||||||
private final HashMap<Jid, JingleCandidate> primaryCandidates = new HashMap<>();
|
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) {
|
} else if (packet.getAction() == JinglePacket.Action.SESSION_INITIATE) {
|
||||||
final Jid from = packet.getFrom();
|
final Jid from = packet.getFrom();
|
||||||
final Content content = packet.getJingleContent();
|
final Content content = packet.getJingleContent();
|
||||||
final String descriptionNamespace = content == null ? null : content.getDescriptionNamespace();
|
final String descriptionNamespace =
|
||||||
|
content == null ? null : content.getDescriptionNamespace();
|
||||||
final AbstractJingleConnection connection;
|
final AbstractJingleConnection connection;
|
||||||
if (FileTransferDescription.NAMESPACES.contains(descriptionNamespace)) {
|
if (FileTransferDescription.NAMESPACES.contains(descriptionNamespace)) {
|
||||||
connection = new JingleFileTransferConnection(this, id, from);
|
connection = new JingleFileTransferConnection(this, id, from);
|
||||||
} else if (Namespace.JINGLE_APPS_RTP.equals(descriptionNamespace) && isUsingClearNet(account)) {
|
} else if (Namespace.JINGLE_APPS_RTP.equals(descriptionNamespace)
|
||||||
final boolean sessionEnded = this.terminatedSessions.asMap().containsKey(PersistableSessionId.of(id));
|
&& isUsingClearNet(account)) {
|
||||||
final boolean stranger = isWithStrangerAndStrangerNotificationsAreOff(account, id.with);
|
final boolean sessionEnded =
|
||||||
|
this.terminatedSessions.asMap().containsKey(PersistableSessionId.of(id));
|
||||||
|
final boolean stranger =
|
||||||
|
isWithStrangerAndStrangerNotificationsAreOff(account, id.with);
|
||||||
if (isBusy() != null || sessionEnded || stranger) {
|
if (isBusy() != null || sessionEnded || stranger) {
|
||||||
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": rejected session with " + id.with + " because busy. sessionEnded=" + sessionEnded + ", stranger=" + stranger);
|
Log.d(
|
||||||
mXmppConnectionService.sendIqPacket(account, packet.generateResponse(IqPacket.TYPE.RESULT), null);
|
Config.LOGTAG,
|
||||||
final JinglePacket sessionTermination = new JinglePacket(JinglePacket.Action.SESSION_TERMINATE, id.sessionId);
|
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.setTo(id.with);
|
||||||
sessionTermination.setReason(Reason.BUSY, null);
|
sessionTermination.setReason(Reason.BUSY, null);
|
||||||
mXmppConnectionService.sendIqPacket(account, sessionTermination, null);
|
mXmppConnectionService.sendIqPacket(account, sessionTermination, null);
|
||||||
|
@ -105,7 +121,8 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
}
|
}
|
||||||
connection = new JingleRtpConnection(this, id, from);
|
connection = new JingleRtpConnection(this, id, from);
|
||||||
} else {
|
} else {
|
||||||
respondWithJingleError(account, packet, "unsupported-info", "feature-not-implemented", "cancel");
|
respondWithJingleError(
|
||||||
|
account, packet, "unsupported-info", "feature-not-implemented", "cancel");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
connections.put(id, connection);
|
connections.put(id, connection);
|
||||||
|
@ -153,12 +170,15 @@ 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) {
|
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 RtpSessionProposal proposal = entry.getKey();
|
||||||
final DeviceDiscoveryState state = entry.getValue();
|
final DeviceDiscoveryState state = entry.getValue();
|
||||||
final boolean openProposal = state == DeviceDiscoveryState.DISCOVERED
|
final boolean openProposal =
|
||||||
|
state == DeviceDiscoveryState.DISCOVERED
|
||||||
|| state == DeviceDiscoveryState.SEARCHING
|
|| state == DeviceDiscoveryState.SEARCHING
|
||||||
|| state == DeviceDiscoveryState.SEARCHING_ACKNOWLEDGED;
|
|| state == DeviceDiscoveryState.SEARCHING_ACKNOWLEDGED;
|
||||||
if (openProposal
|
if (openProposal
|
||||||
|
@ -190,7 +210,8 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isWithStrangerAndStrangerNotificationsAreOff(final Account account, Jid with) {
|
private boolean isWithStrangerAndStrangerNotificationsAreOff(final Account account, Jid with) {
|
||||||
final boolean notifyForStrangers = mXmppConnectionService.getNotificationService().notificationsFromStrangers();
|
final boolean notifyForStrangers =
|
||||||
|
mXmppConnectionService.getNotificationService().notificationsFromStrangers();
|
||||||
if (notifyForStrangers) {
|
if (notifyForStrangers) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -198,11 +219,17 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
return !contact.showInContactList();
|
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);
|
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 IqPacket response = original.generateResponse(IqPacket.TYPE.ERROR);
|
||||||
final Element error = response.addChild("error");
|
final Element error = response.addChild("error");
|
||||||
error.setAttribute("type", conditionType);
|
error.setAttribute("type", conditionType);
|
||||||
|
@ -211,7 +238,14 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
account.getXmppConnection().sendIqPacket(response, null);
|
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()));
|
Preconditions.checkArgument(Namespace.JINGLE_MESSAGE.equals(message.getNamespace()));
|
||||||
final String sessionId = message.getAttribute("id");
|
final String sessionId = message.getAttribute("id");
|
||||||
if (sessionId == null) {
|
if (sessionId == null) {
|
||||||
|
@ -245,16 +279,24 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
final AbstractJingleConnection existingJingleConnection = connections.get(id);
|
final AbstractJingleConnection existingJingleConnection = connections.get(id);
|
||||||
if (existingJingleConnection != null) {
|
if (existingJingleConnection != null) {
|
||||||
if (existingJingleConnection instanceof JingleRtpConnection) {
|
if (existingJingleConnection instanceof JingleRtpConnection) {
|
||||||
((JingleRtpConnection) existingJingleConnection).deliveryMessage(from, message, serverMsgId, timestamp);
|
((JingleRtpConnection) existingJingleConnection)
|
||||||
|
.deliveryMessage(from, message, serverMsgId, timestamp);
|
||||||
} else {
|
} 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fromSelf) {
|
if (fromSelf) {
|
||||||
if ("proceed".equals(message.getName())) {
|
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);
|
final Message previousBusy = c.findRtpSession(sessionId, Message.STATUS_RECEIVED);
|
||||||
if (previousBusy != null) {
|
if (previousBusy != null) {
|
||||||
previousBusy.setBody(new RtpSessionStatus(true, 0).toString());
|
previousBusy.setBody(new RtpSessionStatus(true, 0).toString());
|
||||||
|
@ -263,45 +305,72 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
}
|
}
|
||||||
previousBusy.setTime(timestamp);
|
previousBusy.setTime(timestamp);
|
||||||
mXmppConnectionService.updateMessage(previousBusy, true);
|
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;
|
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)
|
// TODO handle reject for cases where we don’t have carbon copies (normally reject is to
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignore jingle message from self");
|
// be sent to own bare jid as well)
|
||||||
|
Log.d(
|
||||||
|
Config.LOGTAG,
|
||||||
|
account.getJid().asBareJid() + ": ignore jingle message from self");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("propose".equals(message.getName())) {
|
if ("propose".equals(message.getName())) {
|
||||||
final Propose propose = Propose.upgrade(message);
|
final Propose propose = Propose.upgrade(message);
|
||||||
final List<GenericDescription> descriptions = propose.getDescriptions();
|
final List<GenericDescription> descriptions = propose.getDescriptions();
|
||||||
final Collection<RtpDescription> rtpDescriptions = Collections2.transform(
|
final Collection<RtpDescription> rtpDescriptions =
|
||||||
|
Collections2.transform(
|
||||||
Collections2.filter(descriptions, d -> d instanceof RtpDescription),
|
Collections2.filter(descriptions, d -> d instanceof RtpDescription),
|
||||||
input -> (RtpDescription) input
|
input -> (RtpDescription) input);
|
||||||
);
|
if (rtpDescriptions.size() > 0
|
||||||
if (rtpDescriptions.size() > 0 && rtpDescriptions.size() == descriptions.size() && isUsingClearNet(account)) {
|
&& rtpDescriptions.size() == descriptions.size()
|
||||||
final Collection<Media> media = Collections2.transform(rtpDescriptions, RtpDescription::getMedia);
|
&& isUsingClearNet(account)) {
|
||||||
|
final Collection<Media> media =
|
||||||
|
Collections2.transform(rtpDescriptions, RtpDescription::getMedia);
|
||||||
if (media.contains(Media.UNKNOWN)) {
|
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;
|
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()) {
|
if (matchingSessionProposal.isPresent()) {
|
||||||
final String ourSessionId = matchingSessionProposal.get().sessionId;
|
final String ourSessionId = matchingSessionProposal.get().sessionId;
|
||||||
final String theirSessionId = id.sessionId;
|
final String theirSessionId = id.sessionId;
|
||||||
if (ComparisonChain.start()
|
if (ComparisonChain.start()
|
||||||
.compare(ourSessionId, theirSessionId)
|
.compare(ourSessionId, theirSessionId)
|
||||||
.compare(account.getJid().toEscapedString(), id.with.toEscapedString())
|
.compare(
|
||||||
.result() > 0) {
|
account.getJid().toEscapedString(),
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": our session lost tie break. automatically accepting their session. winning Session=" + theirSessionId);
|
id.with.toEscapedString())
|
||||||
//TODO a retract for this reason should probably include some indication of tie break
|
.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());
|
retractSessionProposal(matchingSessionProposal.get());
|
||||||
final JingleRtpConnection rtpConnection = new JingleRtpConnection(this, id, from);
|
final JingleRtpConnection rtpConnection =
|
||||||
|
new JingleRtpConnection(this, id, from);
|
||||||
this.connections.put(id, rtpConnection);
|
this.connections.put(id, rtpConnection);
|
||||||
rtpConnection.setProposedMedia(ImmutableSet.copyOf(media));
|
rtpConnection.setProposedMedia(ImmutableSet.copyOf(media));
|
||||||
rtpConnection.deliveryMessage(from, message, serverMsgId, timestamp);
|
rtpConnection.deliveryMessage(from, message, serverMsgId, timestamp);
|
||||||
} else {
|
} 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;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -309,38 +378,63 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
if (isBusy() != null || stranger) {
|
if (isBusy() != null || stranger) {
|
||||||
writeLogMissedIncoming(account, id.with.asBareJid(), id.sessionId, serverMsgId, timestamp);
|
writeLogMissedIncoming(account, id.with.asBareJid(), id.sessionId, serverMsgId, timestamp);
|
||||||
if (stranger) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
final int activeDevices = account.activeDevicesWithRtpCapability();
|
final int activeDevices = account.activeDevicesWithRtpCapability();
|
||||||
Log.d(Config.LOGTAG, "active devices with rtp capability: " + activeDevices);
|
Log.d(Config.LOGTAG, "active devices with rtp capability: " + activeDevices);
|
||||||
if (activeDevices == 0) {
|
if (activeDevices == 0) {
|
||||||
final MessagePacket reject = mXmppConnectionService.getMessageGenerator().sessionReject(from, sessionId);
|
final MessagePacket reject =
|
||||||
|
mXmppConnectionService
|
||||||
|
.getMessageGenerator()
|
||||||
|
.sessionReject(from, sessionId);
|
||||||
mXmppConnectionService.sendMessagePacket(account, reject);
|
mXmppConnectionService.sendMessagePacket(account, reject);
|
||||||
} else {
|
} 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 {
|
} else {
|
||||||
final JingleRtpConnection rtpConnection = new JingleRtpConnection(this, id, from);
|
final JingleRtpConnection rtpConnection =
|
||||||
|
new JingleRtpConnection(this, id, from);
|
||||||
this.connections.put(id, rtpConnection);
|
this.connections.put(id, rtpConnection);
|
||||||
rtpConnection.setProposedMedia(ImmutableSet.copyOf(media));
|
rtpConnection.setProposedMedia(ImmutableSet.copyOf(media));
|
||||||
rtpConnection.deliveryMessage(from, message, serverMsgId, timestamp);
|
rtpConnection.deliveryMessage(from, message, serverMsgId, timestamp);
|
||||||
}
|
}
|
||||||
} else {
|
} 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())) {
|
} else if (addressedDirectly && "proceed".equals(message.getName())) {
|
||||||
synchronized (rtpSessionProposals) {
|
synchronized (rtpSessionProposals) {
|
||||||
final RtpSessionProposal proposal = getRtpSessionProposal(account, from.asBareJid(), sessionId);
|
final RtpSessionProposal proposal =
|
||||||
|
getRtpSessionProposal(account, from.asBareJid(), sessionId);
|
||||||
if (proposal != null) {
|
if (proposal != null) {
|
||||||
rtpSessionProposals.remove(proposal);
|
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);
|
rtpConnection.setProposedMedia(proposal.media);
|
||||||
this.connections.put(id, rtpConnection);
|
this.connections.put(id, rtpConnection);
|
||||||
rtpConnection.transitionOrThrow(AbstractJingleConnection.State.PROPOSED);
|
rtpConnection.transitionOrThrow(AbstractJingleConnection.State.PROPOSED);
|
||||||
rtpConnection.deliveryMessage(from, message, serverMsgId, timestamp);
|
rtpConnection.deliveryMessage(from, message, serverMsgId, timestamp);
|
||||||
} else {
|
} 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) {
|
if (remoteMsgId == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -356,63 +450,77 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (addressedDirectly && "reject".equals(message.getName())) {
|
} 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) {
|
synchronized (rtpSessionProposals) {
|
||||||
if (proposal != null && rtpSessionProposals.remove(proposal) != null) {
|
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);
|
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 {
|
} 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 {
|
} 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()) {
|
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 rtpSessionProposal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeLogMissedOutgoing(final Account account, Jid with, final String sessionId, String serverMsgId, long timestamp) {
|
private void writeLogMissedOutgoing(
|
||||||
final Conversation conversation = mXmppConnectionService.findOrCreateConversation(
|
final Account account,
|
||||||
account,
|
Jid with,
|
||||||
with.asBareJid(),
|
final String sessionId,
|
||||||
false,
|
String serverMsgId,
|
||||||
false
|
long timestamp) {
|
||||||
);
|
final Conversation conversation =
|
||||||
final Message message = new Message(
|
mXmppConnectionService.findOrCreateConversation(
|
||||||
conversation,
|
account, with.asBareJid(), false, false);
|
||||||
Message.STATUS_SEND,
|
final Message message =
|
||||||
Message.TYPE_RTP_SESSION,
|
new Message(conversation, Message.STATUS_SEND, Message.TYPE_RTP_SESSION, sessionId);
|
||||||
sessionId
|
|
||||||
);
|
|
||||||
message.setBody(new RtpSessionStatus(false, 0).toString());
|
message.setBody(new RtpSessionStatus(false, 0).toString());
|
||||||
message.setServerMsgId(serverMsgId);
|
message.setServerMsgId(serverMsgId);
|
||||||
message.setTime(timestamp);
|
message.setTime(timestamp);
|
||||||
writeMessage(message);
|
writeMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void writeLogMissedIncoming(final Account account, Jid with, final String sessionId, String serverMsgId, long timestamp) {
|
private void writeLogMissedIncoming(
|
||||||
final Conversation conversation = mXmppConnectionService.findOrCreateConversation(
|
final Account account,
|
||||||
account,
|
Jid with,
|
||||||
with.asBareJid(),
|
final String sessionId,
|
||||||
false,
|
String serverMsgId,
|
||||||
false
|
long timestamp) {
|
||||||
);
|
final Conversation conversation =
|
||||||
final Message message = new Message(
|
mXmppConnectionService.findOrCreateConversation(
|
||||||
conversation,
|
account, with.asBareJid(), false, false);
|
||||||
Message.STATUS_RECEIVED,
|
final Message message =
|
||||||
Message.TYPE_RTP_SESSION,
|
new Message(
|
||||||
sessionId
|
conversation, Message.STATUS_RECEIVED, Message.TYPE_RTP_SESSION, sessionId);
|
||||||
);
|
|
||||||
message.setBody(new RtpSessionStatus(false, 0).toString());
|
message.setBody(new RtpSessionStatus(false, 0).toString());
|
||||||
message.setServerMsgId(serverMsgId);
|
message.setServerMsgId(serverMsgId);
|
||||||
message.setTime(timestamp);
|
message.setTime(timestamp);
|
||||||
|
@ -431,34 +539,41 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void startJingleFileTransfer(final Message message) {
|
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();
|
final Transferable old = message.getTransferable();
|
||||||
if (old != null) {
|
if (old != null) {
|
||||||
old.cancel();
|
old.cancel();
|
||||||
}
|
}
|
||||||
final Account account = message.getConversation().getAccount();
|
final Account account = message.getConversation().getAccount();
|
||||||
final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(message);
|
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);
|
mXmppConnectionService.markMessage(message, Message.STATUS_WAITING);
|
||||||
this.connections.put(id, connection);
|
this.connections.put(id, connection);
|
||||||
connection.init(message);
|
connection.init(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<OngoingRtpSession> getOngoingRtpConnection(final Contact contact) {
|
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) {
|
if (entry.getValue() instanceof JingleRtpConnection) {
|
||||||
final AbstractJingleConnection.Id id = entry.getKey();
|
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);
|
return Optional.of(id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
synchronized (this.rtpSessionProposals) {
|
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();
|
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();
|
final DeviceDiscoveryState preexistingState = entry.getValue();
|
||||||
if (preexistingState != null && preexistingState != DeviceDiscoveryState.FAILED) {
|
if (preexistingState != null
|
||||||
|
&& preexistingState != DeviceDiscoveryState.FAILED) {
|
||||||
return Optional.of(proposal);
|
return Optional.of(proposal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -474,7 +589,8 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
void finishConnectionOrThrow(final AbstractJingleConnection connection) {
|
void finishConnectionOrThrow(final AbstractJingleConnection connection) {
|
||||||
final AbstractJingleConnection.Id id = connection.getId();
|
final AbstractJingleConnection.Id id = connection.getId();
|
||||||
if (this.connections.remove(id) == null) {
|
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,33 +609,54 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
return firedUpdates;
|
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) {
|
if (Config.DISABLE_PROXY_LOOKUP) {
|
||||||
listener.onPrimaryCandidateFound(false, null);
|
listener.onPrimaryCandidateFound(false, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.primaryCandidates.containsKey(account.getJid().asBareJid())) {
|
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) {
|
if (proxy != null) {
|
||||||
IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
|
IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
|
||||||
iq.setTo(proxy);
|
iq.setTo(proxy);
|
||||||
iq.query(Namespace.BYTE_STREAMS);
|
iq.query(Namespace.BYTE_STREAMS);
|
||||||
account.getXmppConnection().sendIqPacket(iq, new OnIqPacketReceived() {
|
account.getXmppConnection()
|
||||||
|
.sendIqPacket(
|
||||||
|
iq,
|
||||||
|
new OnIqPacketReceived() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onIqPacketReceived(Account account, IqPacket packet) {
|
public void onIqPacketReceived(
|
||||||
final Element streamhost = packet.query().findChild("streamhost", Namespace.BYTE_STREAMS);
|
Account account, IqPacket packet) {
|
||||||
final String host = streamhost == null ? null : streamhost.getAttribute("host");
|
final Element streamhost =
|
||||||
final String port = streamhost == null ? null : streamhost.getAttribute("port");
|
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) {
|
if (host != null && port != null) {
|
||||||
try {
|
try {
|
||||||
JingleCandidate candidate = new JingleCandidate(nextRandomId(), true);
|
JingleCandidate candidate =
|
||||||
|
new JingleCandidate(nextRandomId(), true);
|
||||||
candidate.setHost(host);
|
candidate.setHost(host);
|
||||||
candidate.setPort(Integer.parseInt(port));
|
candidate.setPort(Integer.parseInt(port));
|
||||||
candidate.setType(JingleCandidate.TYPE_PROXY);
|
candidate.setType(JingleCandidate.TYPE_PROXY);
|
||||||
candidate.setJid(proxy);
|
candidate.setJid(proxy);
|
||||||
candidate.setPriority(655360 + (initiator ? 30 : 0));
|
candidate.setPriority(
|
||||||
primaryCandidates.put(account.getJid().asBareJid(), candidate);
|
655360 + (initiator ? 30 : 0));
|
||||||
|
primaryCandidates.put(
|
||||||
|
account.getJid().asBareJid(), candidate);
|
||||||
listener.onPrimaryCandidateFound(true, candidate);
|
listener.onPrimaryCandidateFound(true, candidate);
|
||||||
} catch (final NumberFormatException e) {
|
} catch (final NumberFormatException e) {
|
||||||
listener.onPrimaryCandidateFound(false, null);
|
listener.onPrimaryCandidateFound(false, null);
|
||||||
|
@ -534,8 +671,8 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
listener.onPrimaryCandidateFound(true,
|
listener.onPrimaryCandidateFound(
|
||||||
this.primaryCandidates.get(account.getJid().asBareJid()));
|
true, this.primaryCandidates.get(account.getJid().asBareJid()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -557,16 +694,28 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
private void retractSessionProposal(RtpSessionProposal rtpSessionProposal) {
|
private void retractSessionProposal(RtpSessionProposal rtpSessionProposal) {
|
||||||
final Account account = rtpSessionProposal.account;
|
final Account account = rtpSessionProposal.account;
|
||||||
toneManager.transition(RtpEndUserState.ENDED, rtpSessionProposal.media);
|
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);
|
this.rtpSessionProposals.remove(rtpSessionProposal);
|
||||||
final MessagePacket messagePacket = mXmppConnectionService.getMessageGenerator().sessionRetract(rtpSessionProposal);
|
final MessagePacket messagePacket =
|
||||||
writeLogMissedOutgoing(account, rtpSessionProposal.with, rtpSessionProposal.sessionId, null, System.currentTimeMillis());
|
mXmppConnectionService.getMessageGenerator().sessionRetract(rtpSessionProposal);
|
||||||
|
writeLogMissedOutgoing(
|
||||||
|
account,
|
||||||
|
rtpSessionProposal.with,
|
||||||
|
rtpSessionProposal.sessionId,
|
||||||
|
null,
|
||||||
|
System.currentTimeMillis());
|
||||||
mXmppConnectionService.sendMessagePacket(account, messagePacket);
|
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 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);
|
rtpConnection.setProposedMedia(media);
|
||||||
this.connections.put(id, rtpConnection);
|
this.connections.put(id, rtpConnection);
|
||||||
rtpConnection.sendSessionInitiate();
|
rtpConnection.sendSessionInitiate();
|
||||||
|
@ -575,11 +724,13 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
|
|
||||||
public String proposeJingleRtpSession(final Account account, final Jid with, final Set<Media> media) {
|
public String proposeJingleRtpSession(final Account account, final Jid with, final Set<Media> media) {
|
||||||
synchronized (this.rtpSessionProposals) {
|
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();
|
RtpSessionProposal proposal = entry.getKey();
|
||||||
if (proposal.account == account && with.asBareJid().equals(proposal.with)) {
|
if (proposal.account == account && with.asBareJid().equals(proposal.with)) {
|
||||||
final DeviceDiscoveryState preexistingState = entry.getValue();
|
final DeviceDiscoveryState preexistingState = entry.getValue();
|
||||||
if (preexistingState != null && preexistingState != DeviceDiscoveryState.FAILED) {
|
if (preexistingState != null
|
||||||
|
&& preexistingState != DeviceDiscoveryState.FAILED) {
|
||||||
final RtpEndUserState endUserState = preexistingState.toEndUserState();
|
final RtpEndUserState endUserState = preexistingState.toEndUserState();
|
||||||
toneManager.transition(endUserState, media);
|
toneManager.transition(endUserState, media);
|
||||||
mXmppConnectionService.notifyJingleRtpConnectionUpdate(
|
mXmppConnectionService.notifyJingleRtpConnectionUpdate(
|
||||||
|
@ -601,15 +752,13 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
}
|
}
|
||||||
throw new IllegalStateException("There is already a running RTP session: " + busyCode);
|
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);
|
this.rtpSessionProposals.put(proposal, DeviceDiscoveryState.SEARCHING);
|
||||||
mXmppConnectionService.notifyJingleRtpConnectionUpdate(
|
mXmppConnectionService.notifyJingleRtpConnectionUpdate(
|
||||||
account,
|
account, proposal.with, proposal.sessionId, RtpEndUserState.FINDING_DEVICE);
|
||||||
proposal.with,
|
final MessagePacket messagePacket =
|
||||||
proposal.sessionId,
|
mXmppConnectionService.getMessageGenerator().sessionProposal(proposal);
|
||||||
RtpEndUserState.FINDING_DEVICE
|
|
||||||
);
|
|
||||||
final MessagePacket messagePacket = mXmppConnectionService.getMessageGenerator().sessionProposal(proposal);
|
|
||||||
mXmppConnectionService.sendMessagePacket(account, messagePacket);
|
mXmppConnectionService.sendMessagePacket(account, messagePacket);
|
||||||
return proposal.sessionId;
|
return proposal.sessionId;
|
||||||
}
|
}
|
||||||
|
@ -617,7 +766,8 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
|
|
||||||
public boolean hasMatchingProposal(final Account account, final Jid with) {
|
public boolean hasMatchingProposal(final Account account, final Jid with) {
|
||||||
synchronized (this.rtpSessionProposals) {
|
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 RtpSessionProposal proposal = entry.getKey();
|
||||||
if (proposal.account == account && with.asBareJid().equals(proposal.with)) {
|
if (proposal.account == account && with.asBareJid().equals(proposal.with)) {
|
||||||
return true;
|
return true;
|
||||||
|
@ -646,10 +796,12 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
if (sid != null) {
|
if (sid != null) {
|
||||||
for (final AbstractJingleConnection connection : this.connections.values()) {
|
for (final AbstractJingleConnection connection : this.connections.values()) {
|
||||||
if (connection instanceof JingleFileTransferConnection) {
|
if (connection instanceof JingleFileTransferConnection) {
|
||||||
final JingleFileTransferConnection fileTransfer = (JingleFileTransferConnection) connection;
|
final JingleFileTransferConnection fileTransfer =
|
||||||
|
(JingleFileTransferConnection) connection;
|
||||||
final JingleTransport transport = fileTransfer.getTransport();
|
final JingleTransport transport = fileTransfer.getTransport();
|
||||||
if (transport instanceof JingleInBandTransport) {
|
if (transport instanceof JingleInBandTransport) {
|
||||||
final JingleInBandTransport inBandTransport = (JingleInBandTransport) transport;
|
final JingleInBandTransport inBandTransport =
|
||||||
|
(JingleInBandTransport) transport;
|
||||||
if (inBandTransport.matches(account, sid)) {
|
if (inBandTransport.matches(account, sid)) {
|
||||||
inBandTransport.deliverPayload(packet, payload);
|
inBandTransport.deliverPayload(packet, payload);
|
||||||
}
|
}
|
||||||
|
@ -659,7 +811,8 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Log.d(Config.LOGTAG, "unable to deliver ibb packet: " + packet.toString());
|
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) {
|
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) {
|
public WeakReference<JingleRtpConnection> findJingleRtpConnection(
|
||||||
final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(account, with, sessionId);
|
Account account, Jid with, String sessionId) {
|
||||||
|
final AbstractJingleConnection.Id id =
|
||||||
|
AbstractJingleConnection.Id.of(account, with, sessionId);
|
||||||
final AbstractJingleConnection connection = connections.get(id);
|
final AbstractJingleConnection connection = connections.get(id);
|
||||||
if (connection instanceof JingleRtpConnection) {
|
if (connection instanceof JingleRtpConnection) {
|
||||||
return new WeakReference<>((JingleRtpConnection) connection);
|
return new WeakReference<>((JingleRtpConnection) connection);
|
||||||
|
@ -683,34 +838,53 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
|
|
||||||
private void resendSessionProposals(final Account account) {
|
private void resendSessionProposals(final Account account) {
|
||||||
synchronized (this.rtpSessionProposals) {
|
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();
|
final RtpSessionProposal proposal = entry.getKey();
|
||||||
if (entry.getValue() == DeviceDiscoveryState.SEARCHING && proposal.account == account) {
|
if (entry.getValue() == DeviceDiscoveryState.SEARCHING
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": resending session proposal to " + proposal.with);
|
&& proposal.account == account) {
|
||||||
final MessagePacket messagePacket = mXmppConnectionService.getMessageGenerator().sessionProposal(proposal);
|
Log.d(
|
||||||
|
Config.LOGTAG,
|
||||||
|
account.getJid().asBareJid()
|
||||||
|
+ ": resending session proposal to "
|
||||||
|
+ proposal.with);
|
||||||
|
final MessagePacket messagePacket =
|
||||||
|
mXmppConnectionService.getMessageGenerator().sessionProposal(proposal);
|
||||||
mXmppConnectionService.sendMessagePacket(account, messagePacket);
|
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) {
|
synchronized (this.rtpSessionProposals) {
|
||||||
final RtpSessionProposal sessionProposal = getRtpSessionProposal(account, from.asBareJid(), sessionId);
|
final RtpSessionProposal sessionProposal =
|
||||||
final DeviceDiscoveryState currentState = sessionProposal == null ? null : rtpSessionProposals.get(sessionProposal);
|
getRtpSessionProposal(account, from.asBareJid(), sessionId);
|
||||||
|
final DeviceDiscoveryState currentState =
|
||||||
|
sessionProposal == null ? null : rtpSessionProposals.get(sessionProposal);
|
||||||
if (currentState == null) {
|
if (currentState == null) {
|
||||||
Log.d(Config.LOGTAG, "unable to find session proposal for session id " + sessionId);
|
Log.d(Config.LOGTAG, "unable to find session proposal for session id " + sessionId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (currentState == DeviceDiscoveryState.DISCOVERED) {
|
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;
|
return;
|
||||||
}
|
}
|
||||||
this.rtpSessionProposals.put(sessionProposal, target);
|
this.rtpSessionProposals.put(sessionProposal, target);
|
||||||
final RtpEndUserState endUserState = target.toEndUserState();
|
final RtpEndUserState endUserState = target.toEndUserState();
|
||||||
toneManager.transition(endUserState, sessionProposal.media);
|
toneManager.transition(endUserState, sessionProposal.media);
|
||||||
mXmppConnectionService.notifyJingleRtpConnectionUpdate(account, sessionProposal.with, sessionProposal.sessionId, endUserState);
|
mXmppConnectionService.notifyJingleRtpConnectionUpdate(
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": flagging session " + sessionId + " as " + target);
|
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) {
|
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);
|
final AbstractJingleConnection existingJingleConnection = connections.get(id);
|
||||||
if (existingJingleConnection instanceof JingleRtpConnection) {
|
if (existingJingleConnection instanceof JingleRtpConnection) {
|
||||||
((JingleRtpConnection) existingJingleConnection).deliverFailedProceed();
|
((JingleRtpConnection) existingJingleConnection).deliverFailedProceed();
|
||||||
|
@ -746,13 +921,17 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
if (connections.containsValue(connection)) {
|
if (connections.containsValue(connection)) {
|
||||||
return;
|
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);
|
Log.e(Config.LOGTAG, "ensureConnectionIsRegistered() failed. Going to throw", e);
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
void setTerminalSessionState(AbstractJingleConnection.Id id, final RtpEndUserState state, final Set<Media> media) {
|
void setTerminalSessionState(
|
||||||
this.terminatedSessions.put(PersistableSessionId.of(id), new TerminatedRtpSession(state, media));
|
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) {
|
public TerminatedRtpSession getTerminalSessionState(final Jid with, final String sessionId) {
|
||||||
|
@ -777,8 +956,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
PersistableSessionId that = (PersistableSessionId) o;
|
PersistableSessionId that = (PersistableSessionId) o;
|
||||||
return Objects.equal(with, that.with) &&
|
return Objects.equal(with, that.with) && Objects.equal(sessionId, that.sessionId);
|
||||||
Objects.equal(sessionId, that.sessionId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -798,7 +976,10 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum DeviceDiscoveryState {
|
public enum DeviceDiscoveryState {
|
||||||
SEARCHING, SEARCHING_ACKNOWLEDGED, DISCOVERED, FAILED;
|
SEARCHING,
|
||||||
|
SEARCHING_ACKNOWLEDGED,
|
||||||
|
DISCOVERED,
|
||||||
|
FAILED;
|
||||||
|
|
||||||
public RtpEndUserState toEndUserState() {
|
public RtpEndUserState toEndUserState() {
|
||||||
switch (this) {
|
switch (this) {
|
||||||
|
@ -839,9 +1020,9 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
RtpSessionProposal proposal = (RtpSessionProposal) o;
|
RtpSessionProposal proposal = (RtpSessionProposal) o;
|
||||||
return Objects.equal(account.getJid(), proposal.account.getJid()) &&
|
return Objects.equal(account.getJid(), proposal.account.getJid())
|
||||||
Objects.equal(with, proposal.with) &&
|
&& Objects.equal(with, proposal.with)
|
||||||
Objects.equal(sessionId, proposal.sessionId);
|
&& Objects.equal(sessionId, proposal.sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -492,19 +492,19 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple
|
||||||
AbstractConnectionManager.Extension extension = AbstractConnectionManager.Extension.of(path);
|
AbstractConnectionManager.Extension extension = AbstractConnectionManager.Extension.of(path);
|
||||||
if (VALID_IMAGE_EXTENSIONS.contains(extension.main)) {
|
if (VALID_IMAGE_EXTENSIONS.contains(extension.main)) {
|
||||||
message.setType(Message.TYPE_IMAGE);
|
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)) {
|
} else if (VALID_CRYPTO_EXTENSIONS.contains(extension.main)) {
|
||||||
if (VALID_IMAGE_EXTENSIONS.contains(extension.secondary)) {
|
if (VALID_IMAGE_EXTENSIONS.contains(extension.secondary)) {
|
||||||
message.setType(Message.TYPE_IMAGE);
|
message.setType(Message.TYPE_IMAGE);
|
||||||
message.setRelativeFilePath(message.getUuid() + "." + extension.secondary);
|
xmppConnectionService.getFileBackend().setupRelativeFilePath(message,message.getUuid() + "." + extension.secondary);
|
||||||
} else {
|
} else {
|
||||||
message.setType(Message.TYPE_FILE);
|
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);
|
message.setEncryption(Message.ENCRYPTION_PGP);
|
||||||
} else {
|
} else {
|
||||||
message.setType(Message.TYPE_FILE);
|
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);
|
long size = parseLong(fileSize, 0);
|
||||||
message.setBody(Long.toString(size));
|
message.setBody(Long.toString(size));
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -16,7 +16,6 @@ import org.checkerframework.checker.nullness.compatqual.NullableDecl;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
|
||||||
|
@ -39,7 +38,8 @@ public class RtpContentMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RtpContentMap of(final JinglePacket jinglePacket) {
|
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)) {
|
if (isOmemoVerified(contents)) {
|
||||||
return new OmemoVerifiedRtpContentMap(jinglePacket.getGroup(), contents);
|
return new OmemoVerifiedRtpContentMap(jinglePacket.getGroup(), contents);
|
||||||
} else {
|
} else {
|
||||||
|
@ -62,21 +62,29 @@ public class RtpContentMap {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RtpContentMap of(final SessionDescription sessionDescription) {
|
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) {
|
for (SessionDescription.Media media : sessionDescription.media) {
|
||||||
final String id = Iterables.getFirst(media.attributes.get("mid"), null);
|
final String id = Iterables.getFirst(media.attributes.get("mid"), null);
|
||||||
Preconditions.checkNotNull(id, "media has no mid");
|
Preconditions.checkNotNull(id, "media has no mid");
|
||||||
contentMapBuilder.put(id, DescriptionTransport.of(sessionDescription, media));
|
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);
|
final Group group = groupAttribute == null ? null : Group.ofSdpString(groupAttribute);
|
||||||
return new RtpContentMap(group, contentMapBuilder.build());
|
return new RtpContentMap(group, contentMapBuilder.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<Media> getMedia() {
|
public Set<Media> getMedia() {
|
||||||
return Sets.newHashSet(Collections2.transform(contents.values(), input -> {
|
return Sets.newHashSet(
|
||||||
final RtpDescription rtpDescription = input == null ? null : input.description;
|
Collections2.transform(
|
||||||
return rtpDescription == null ? Media.UNKNOWN : input.description.getMedia();
|
contents.values(),
|
||||||
|
input -> {
|
||||||
|
final RtpDescription rtpDescription =
|
||||||
|
input == null ? null : input.description;
|
||||||
|
return rtpDescription == null
|
||||||
|
? Media.UNKNOWN
|
||||||
|
: input.description.getMedia();
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +98,8 @@ public class RtpContentMap {
|
||||||
}
|
}
|
||||||
for (Map.Entry<String, DescriptionTransport> entry : this.contents.entrySet()) {
|
for (Map.Entry<String, DescriptionTransport> entry : this.contents.entrySet()) {
|
||||||
if (entry.getValue().description == null) {
|
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()) {
|
for (Map.Entry<String, DescriptionTransport> entry : this.contents.entrySet()) {
|
||||||
final IceUdpTransportInfo transport = entry.getValue().transport;
|
final IceUdpTransportInfo transport = entry.getValue().transport;
|
||||||
final IceUdpTransportInfo.Fingerprint fingerprint = transport.getFingerprint();
|
final IceUdpTransportInfo.Fingerprint fingerprint = transport.getFingerprint();
|
||||||
if (fingerprint == null || Strings.isNullOrEmpty(fingerprint.getContent()) || Strings.isNullOrEmpty(fingerprint.getHash())) {
|
if (fingerprint == null
|
||||||
throw new SecurityException(String.format("Use of DTLS-SRTP (XEP-0320) is required for content %s", entry.getKey()));
|
|| 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();
|
final IceUdpTransportInfo.Setup setup = fingerprint.getSetup();
|
||||||
if (setup == null) {
|
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) {
|
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;
|
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 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) {
|
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();
|
final IceUdpTransportInfo newTransportInfo = transportInfo.cloneWrapper();
|
||||||
newTransportInfo.addChild(candidate);
|
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() {
|
RtpContentMap transportInfo() {
|
||||||
return new RtpContentMap(
|
return new RtpContentMap(
|
||||||
null,
|
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() {
|
public IceUdpTransportInfo.Credentials getDistinctCredentials() {
|
||||||
final Set<IceUdpTransportInfo.Credentials> allCredentials = ImmutableSet.copyOf(Collections2.transform(
|
final Set<IceUdpTransportInfo.Credentials> allCredentials = getCredentials();
|
||||||
contents.values(),
|
final IceUdpTransportInfo.Credentials credentials =
|
||||||
dt -> dt.transport.getCredentials()
|
Iterables.getFirst(allCredentials, null);
|
||||||
));
|
|
||||||
final IceUdpTransportInfo.Credentials credentials = Iterables.getFirst(allCredentials, null);
|
|
||||||
if (allCredentials.size() == 1 && credentials != null) {
|
if (allCredentials.size() == 1 && credentials != null) {
|
||||||
return credentials;
|
return credentials;
|
||||||
}
|
}
|
||||||
throw new IllegalStateException("Content map does not have distinct 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() {
|
public IceUdpTransportInfo.Setup getDtlsSetup() {
|
||||||
final Set<IceUdpTransportInfo.Setup> setups = ImmutableSet.copyOf(Collections2.transform(
|
final Set<IceUdpTransportInfo.Setup> setups =
|
||||||
contents.values(),
|
ImmutableSet.copyOf(
|
||||||
dt -> dt.transport.getFingerprint().getSetup()
|
Collections2.transform(
|
||||||
));
|
contents.values(), dt -> dt.transport.getFingerprint().getSetup()));
|
||||||
final IceUdpTransportInfo.Setup setup = Iterables.getFirst(setups, null);
|
final IceUdpTransportInfo.Setup setup = Iterables.getFirst(setups, null);
|
||||||
if (setups.size() == 1 && setup != null) {
|
if (setups.size() == 1 && setup != null) {
|
||||||
return setup;
|
return setup;
|
||||||
|
@ -185,13 +228,18 @@ public class RtpContentMap {
|
||||||
return count == 0;
|
return count == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
public RtpContentMap modifiedCredentials(IceUdpTransportInfo.Credentials credentials, final IceUdpTransportInfo.Setup setup) {
|
public RtpContentMap modifiedCredentials(
|
||||||
final ImmutableMap.Builder<String, DescriptionTransport> contentMapBuilder = new ImmutableMap.Builder<>();
|
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()) {
|
for (final Map.Entry<String, DescriptionTransport> content : contents.entrySet()) {
|
||||||
final RtpDescription rtpDescription = content.getValue().description;
|
final RtpDescription rtpDescription = content.getValue().description;
|
||||||
IceUdpTransportInfo transportInfo = content.getValue().transport;
|
IceUdpTransportInfo transportInfo = content.getValue().transport;
|
||||||
final IceUdpTransportInfo modifiedTransportInfo = transportInfo.modifyCredentials(credentials, setup);
|
final IceUdpTransportInfo modifiedTransportInfo =
|
||||||
contentMapBuilder.put(content.getKey(), new DescriptionTransport(rtpDescription, modifiedTransportInfo));
|
transportInfo.modifyCredentials(credentials, setup);
|
||||||
|
contentMapBuilder.put(
|
||||||
|
content.getKey(),
|
||||||
|
new DescriptionTransport(rtpDescription, modifiedTransportInfo));
|
||||||
}
|
}
|
||||||
return new RtpContentMap(this.group, contentMapBuilder.build());
|
return new RtpContentMap(this.group, contentMapBuilder.build());
|
||||||
}
|
}
|
||||||
|
@ -200,7 +248,8 @@ public class RtpContentMap {
|
||||||
public final RtpDescription description;
|
public final RtpDescription description;
|
||||||
public final IceUdpTransportInfo transport;
|
public final IceUdpTransportInfo transport;
|
||||||
|
|
||||||
public DescriptionTransport(final RtpDescription description, final IceUdpTransportInfo transport) {
|
public DescriptionTransport(
|
||||||
|
final RtpDescription description, final IceUdpTransportInfo transport) {
|
||||||
this.description = description;
|
this.description = description;
|
||||||
this.transport = transport;
|
this.transport = transport;
|
||||||
}
|
}
|
||||||
|
@ -215,27 +264,32 @@ public class RtpContentMap {
|
||||||
} else if (description instanceof RtpDescription) {
|
} else if (description instanceof RtpDescription) {
|
||||||
rtpDescription = (RtpDescription) description;
|
rtpDescription = (RtpDescription) description;
|
||||||
} else {
|
} else {
|
||||||
throw new UnsupportedApplicationException("Content does not contain rtp description");
|
throw new UnsupportedApplicationException(
|
||||||
|
"Content does not contain rtp description");
|
||||||
}
|
}
|
||||||
if (transportInfo instanceof IceUdpTransportInfo) {
|
if (transportInfo instanceof IceUdpTransportInfo) {
|
||||||
iceUdpTransportInfo = (IceUdpTransportInfo) transportInfo;
|
iceUdpTransportInfo = (IceUdpTransportInfo) transportInfo;
|
||||||
} else {
|
} else {
|
||||||
throw new UnsupportedTransportException("Content does not contain ICE-UDP transport");
|
throw new UnsupportedTransportException(
|
||||||
|
"Content does not contain ICE-UDP transport");
|
||||||
}
|
}
|
||||||
return new DescriptionTransport(
|
return new DescriptionTransport(
|
||||||
rtpDescription,
|
rtpDescription, OmemoVerifiedIceUdpTransportInfo.upgrade(iceUdpTransportInfo));
|
||||||
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 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);
|
return new DescriptionTransport(rtpDescription, transportInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Map<String, DescriptionTransport> of(final Map<String, Content> contents) {
|
public static Map<String, DescriptionTransport> of(final Map<String, Content> contents) {
|
||||||
return ImmutableMap.copyOf(Maps.transformValues(contents, new Function<Content, DescriptionTransport>() {
|
return ImmutableMap.copyOf(
|
||||||
|
Maps.transformValues(
|
||||||
|
contents,
|
||||||
|
new Function<Content, DescriptionTransport>() {
|
||||||
@NullableDecl
|
@NullableDecl
|
||||||
@Override
|
@Override
|
||||||
public DescriptionTransport apply(@NullableDecl Content content) {
|
public DescriptionTransport apply(@NullableDecl Content content) {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package eu.siacs.conversations.xmpp.jingle.stanzas;
|
package eu.siacs.conversations.xmpp.jingle.stanzas;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
import com.google.common.base.MoreObjects;
|
import com.google.common.base.MoreObjects;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
|
@ -123,6 +125,7 @@ public class IceUdpTransportInfo extends GenericTransportInfo {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@NonNull
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return MoreObjects.toStringHelper(this)
|
return MoreObjects.toStringHelper(this)
|
||||||
.add("ufrag", ufrag)
|
.add("ufrag", ufrag)
|
||||||
|
|
|
@ -106,7 +106,7 @@
|
||||||
app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error"
|
app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error"
|
||||||
app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint">
|
app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint">
|
||||||
|
|
||||||
<eu.siacs.conversations.ui.widget.EmojiWrapperEditText
|
<EditText
|
||||||
android:id="@+id/muc_edit_title"
|
android:id="@+id/muc_edit_title"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
@ -120,7 +120,7 @@
|
||||||
app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error"
|
app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error"
|
||||||
app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint">
|
app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint">
|
||||||
|
|
||||||
<eu.siacs.conversations.ui.widget.EmojiWrapperEditText
|
<EditText
|
||||||
android:id="@+id/muc_edit_subject"
|
android:id="@+id/muc_edit_subject"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|
|
@ -55,23 +55,21 @@
|
||||||
android:layout_marginLeft="16dp"
|
android:layout_marginLeft="16dp"
|
||||||
android:layout_marginTop="0dp"
|
android:layout_marginTop="0dp"
|
||||||
android:layout_marginRight="16dp"
|
android:layout_marginRight="16dp"
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="8dp"
|
||||||
android:textAppearance="@style/TextAppearance.Conversations.Display2"
|
android:textAppearance="@style/TextAppearance.Conversations.Display2"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
tools:text="Juliet Capulet" />
|
tools:text="Juliet Capulet" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/with_jid"
|
android:id="@+id/with_jid"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_below="@id/with"
|
android:layout_below="@id/status"
|
||||||
android:layout_marginLeft="16dp"
|
android:layout_marginLeft="16dp"
|
||||||
android:layout_marginTop="0dp"
|
|
||||||
android:layout_marginRight="16dp"
|
android:layout_marginRight="16dp"
|
||||||
android:layout_marginBottom="32dp"
|
android:layout_marginBottom="32dp"
|
||||||
android:textAppearance="@style/TextAppearance.Conversations.Title"
|
android:textAppearance="@style/TextAppearance.Conversations.Body1"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
tools:text="juliet@capulet.lit" />
|
tools:text="jcapulet@example.com" />
|
||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
@ -256,5 +254,15 @@
|
||||||
app:tint="?attr/icon_tint" />
|
app:tint="?attr/icon_tint" />
|
||||||
</RelativeLayout>
|
</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>
|
</RelativeLayout>
|
||||||
</layout>
|
</layout>
|
||||||
|
|
|
@ -28,7 +28,7 @@
|
||||||
app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error"
|
app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error"
|
||||||
app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint">
|
app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint">
|
||||||
|
|
||||||
<eu.siacs.conversations.ui.widget.EmojiWrapperEditText
|
<EditText
|
||||||
android:id="@+id/group_chat_name"
|
android:id="@+id/group_chat_name"
|
||||||
style="@style/Widget.Conversations.EditText"
|
style="@style/Widget.Conversations.EditText"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
@ -26,7 +26,7 @@
|
||||||
app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error"
|
app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error"
|
||||||
app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint">
|
app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint">
|
||||||
|
|
||||||
<eu.siacs.conversations.ui.widget.EmojiWrapperEditText
|
<EditText
|
||||||
android:id="@+id/group_chat_name"
|
android:id="@+id/group_chat_name"
|
||||||
style="@style/Widget.Conversations.EditText"
|
style="@style/Widget.Conversations.EditText"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error"
|
app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error"
|
||||||
app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint">
|
app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint">
|
||||||
|
|
||||||
<eu.siacs.conversations.ui.widget.EmojiWrapperEditText
|
<EditText
|
||||||
android:id="@+id/input_edit_text"
|
android:id="@+id/input_edit_text"
|
||||||
style="@style/Widget.Conversations.EditText"
|
style="@style/Widget.Conversations.EditText"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
@ -25,7 +25,7 @@
|
||||||
android:inputType="textPersonName">
|
android:inputType="textPersonName">
|
||||||
|
|
||||||
<requestFocus/>
|
<requestFocus/>
|
||||||
</eu.siacs.conversations.ui.widget.EmojiWrapperEditText>
|
</EditText>
|
||||||
|
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
|
@ -965,5 +965,4 @@
|
||||||
<string name="backup_started_message">Създаването на резервно копие е стартирано. Ще получите известие, когато приключи.</string>
|
<string name="backup_started_message">Създаването на резервно копие е стартирано. Ще получите известие, когато приключи.</string>
|
||||||
<string name="unable_to_enable_video">Видеото не може да бъде включено.</string>
|
<string name="unable_to_enable_video">Видеото не може да бъде включено.</string>
|
||||||
<string name="plain_text_document">Обикновен текстов документ</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="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="unable_to_enable_video">Kunne ikke aktivere video.</string>
|
||||||
<string name="plain_text_document">Ren tekstdokument</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="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="unable_to_enable_video">Video kann nicht aktiviert werden.</string>
|
||||||
<string name="plain_text_document">Textdokument</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="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="unable_to_enable_video">No se ha podido habilitar el vídeo.</string>
|
||||||
<string name="plain_text_document">Documento de texto plano</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="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="unable_to_enable_video">Videon käyttöönotto epäonnistui</string>
|
||||||
<string name="plain_text_document">Perustekstiasiakirja</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">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="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="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="unable_to_parse_certificate">Non se puido procesar o certificado</string>
|
||||||
<string name="mam_prefs">Gardando axustes</string>
|
<string name="mam_prefs">Gardando axustes</string>
|
||||||
<string name="server_side_mam_prefs">Axustes de gardado no servidor</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="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="unable_to_enable_video">Non se puido activar o vídeo.</string>
|
||||||
<string name="plain_text_document">Documento de texto plano</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="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="unable_to_enable_video">Impossibile attivare il video.</string>
|
||||||
<string name="plain_text_document">Documento di testo</string>
|
<string name="plain_text_document">Documento di testo</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -950,5 +950,4 @@
|
||||||
<string name="backup_started_message">バックアップを開始しました。 バックアップが完了すると通知が届きます。</string>
|
<string name="backup_started_message">バックアップを開始しました。 バックアップが完了すると通知が届きます。</string>
|
||||||
<string name="unable_to_enable_video">映像を有効化できません。</string>
|
<string name="unable_to_enable_video">映像を有効化できません。</string>
|
||||||
<string name="plain_text_document">プレーンテキスト文書</string>
|
<string name="plain_text_document">プレーンテキスト文書</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -431,7 +431,7 @@
|
||||||
<string name="offering_x_file">Oferowanie %s</string>
|
<string name="offering_x_file">Oferowanie %s</string>
|
||||||
<string name="hide_offline">Ukryj niedostępnych</string>
|
<string name="hide_offline">Ukryj niedostępnych</string>
|
||||||
<string name="contact_is_typing">%s pisze...</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_are_typing">%s piszą...</string>
|
||||||
<string name="contacts_have_stopped_typing">%s przestali pisać</string>
|
<string name="contacts_have_stopped_typing">%s przestali pisać</string>
|
||||||
<string name="pref_chat_states">Powiadomienia pisania</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="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="unable_to_enable_video">Nie można włączyć wideo. </string>
|
||||||
<string name="plain_text_document">Dokument zwykłego tekstu</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="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="unable_to_enable_video">Não foi possível habilitar o vídeo.</string>
|
||||||
<string name="plain_text_document">Documento em texto puro</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="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="unable_to_enable_video">Nu s-a putut activa camera video.</string>
|
||||||
<string name="plain_text_document">Document text</string>
|
<string name="plain_text_document">Document text</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -993,5 +993,4 @@
|
||||||
<string name="backup_started_message">Резервное копирование было начато. Вы получите уведомление, как только оно будет завершено. </string>
|
<string name="backup_started_message">Резервное копирование было начато. Вы получите уведомление, как только оно будет завершено. </string>
|
||||||
<string name="unable_to_enable_video">Невозможно включить видео.</string>
|
<string name="unable_to_enable_video">Невозможно включить видео.</string>
|
||||||
<string name="plain_text_document">Текстовые данные</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="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="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="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="your_full_jid_will_be">Din fullständiga XMPP-adress kommer att vara: %s</string>
|
||||||
<string name="create_account">Skapa konto</string>
|
<string name="create_account">Skapa konto</string>
|
||||||
<string name="use_own_provider">Använd min egen leverantör</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="yesterday">Igår</string>
|
||||||
<string name="pref_validate_hostname">Bekräfta värdnamn med DNSSEC</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="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="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_copied_to_clipboard">Meddelande kopierat till urklipp</string>
|
||||||
<string name="message">Meddelande</string>
|
<string name="message">Meddelande</string>
|
||||||
<string name="mtm_accept_cert">Godkänn okänt certifikat?</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="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="unable_to_enable_video">Video etkinleştirilemedi</string>
|
||||||
<string name="plain_text_document">Düz metin dosyası</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="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="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>
|
<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="backup_started_message">已启动备份。一旦完成,你会收到通知。</string>
|
||||||
<string name="unable_to_enable_video">无法启用视频</string>
|
<string name="unable_to_enable_video">无法启用视频</string>
|
||||||
<string name="plain_text_document">纯文本文档</string>
|
<string name="plain_text_document">纯文本文档</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
<resources>
|
<resources>
|
||||||
<string name="pref_about_message" translatable="false">
|
<string name="pref_about_message" translatable="false">
|
||||||
Conversations • the very last word in instant messaging.
|
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
|
\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
|
it under the terms of the GNU General Public License as published by
|
||||||
the Free Software Foundation, either version 3 of the License, or
|
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">
|
|
||||||
<item>MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK</item>
|
|
||||||
</string-array>
|
|
||||||
</resources>
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue