diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4e748a2cf7e46a19e89381c02d58a30d0e081d82..ac29b5f883dd4a74a1b369567e61eaf9489975fe 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,14 @@
# Changelog
+### Version 2.10.9
+
+* Ask for Bluetooth permissions when making A/V calls (You can reject this if you don’t use Bluetooth headsets)
+* Fix bug when calling Movim
+
+### Version 2.10.8
+
+* Fix wrong avatar being shown for group chats
+
### Version 2.10.7
* always ask for battery optimizations opt-out
diff --git a/build.gradle b/build.gradle
index a2fc0ea1c75902a19a5e94846abc0e68cfae01aa..a066f05530ec09d3186684505ce3eeccccd497fc 100644
--- a/build.gradle
+++ b/build.gradle
@@ -103,7 +103,7 @@ dependencies {
implementation 'io.michaelrocks:libphonenumber-android:8.12.49'
implementation 'io.github.nishkarsh:android-permissions:2.1.6'
implementation 'androidx.recyclerview:recyclerview:1.1.0'
- implementation urlFile('https://cloudflare-ipfs.com/ipfs/QmeqMiLxHi8AAjXobxr3QTfa1bSSLyAu86YviAqQnjxCjM/libwebrtc.aar', 'libwebrtc.aar')
+ implementation 'ch.threema:webrtc-android:100.0.0'
// INSERT
}
diff --git a/docs/user/backup.md b/docs/user/backup.md
new file mode 100644
index 0000000000000000000000000000000000000000..4d81d8ddda914502d4cbd9901a896b8684a8f7f0
--- /dev/null
+++ b/docs/user/backup.md
@@ -0,0 +1,19 @@
+# Making a backup of Conversations
+
+This tutorial explains how you can backup your Conversations data.
+
+**WARNING**: Do not use the restore backup feature in an attempt to clone (run simultaneously) an installation. Restoring a backup is only meant for migrations or in case you’ve lost the original device.
+
+1. Make sure that you know the password to your account(s)! You will need it later to decrypt your backup.
+2. Deactivate all your account(s): on the chat screen, tap on the three buttons in the upper right, and go to "manage accounts".
+3. Go back to Settings, scroll down until you find the option to create a new backup. Tap on that option.
+4. Wait, until the notification tells you that the backup is finished.
+5. Move the backup to whatever location you feel save with.
+
+Done!
+
+## Further information / troubleshooting
+### Unable to decrypt
+This backup method will include your OMEMO keys. Due to forward secrecy you will not be able to recover messages sent and received between creating the backup and restoring it. If you have a server side archive (MAM) those messages will be retrieved but displayed as *unable to decrypt*. For technical reasons you might also lose the first message you either sent or receive after the restore; for each conversation you have. This message will then also show up as *unable to decrypt*, but this will automatically recover itself as long as both participants are on Conversations 2.3.11+. Note that this doesn’t happen if you just transfer to a new phone and no messages have been exchanged between backup and restore.
+
+In the vast, vast majority of cases you won’t have to manually delete OMEMO keys or do anything like that. Conversations only introduced the official backup feature in 2.4.0 after making sure the *OMEMO self healing* mechanism introduced in 2.3.11 works fine.
diff --git a/docs/user/migrating_to_new_device.md b/docs/user/migrating_to_new_device.md
new file mode 100644
index 0000000000000000000000000000000000000000..401a153867a4bd4286b6091e23d90a2a9267d373
--- /dev/null
+++ b/docs/user/migrating_to_new_device.md
@@ -0,0 +1,42 @@
+# Migrating to a new device
+
+This tutorial explains how you can transfer your Conversations data from an old to a new device. It assumes that you do not have Conversations installed on your new device, yet. It basically consists of three steps:
+
+1. Make a backup (old device)
+2. Move that backup to your new device
+3. Import the backup (new device)
+
+**WARNING**: Do not use the restore backup feature in an attempt to clone (run simultaneously) an installation. Restoring a backup is only meant for migrations or in case you’ve lost the original device.
+
+## 1. Make a backup (old device)
+1. Make sure that you know the password to your account(s)! You will need it later to decrypt your backup.
+2. Deactivate all your account(s): on the chat screen, tap on the three buttons in the upper right, and go to "manage accounts".
+3. Go back to Settings, scroll down until you find the option to create a new backup. Tap on that option.
+4. Wait, until the notification tells you that the backup is finished.
+
+## 2. Move that backup to your new device
+1. Locate the backup. You should find it in your Files, either in *Conversations/Backup* or in *Download/Conversations/Backup*. The file is named after your account (*e.g. kim@example.org*). If you have multiple accounts, you find one file for each.
+2. Use your USB cable or bluetooth, your Nextcloud or other cloud storage or pretty much anything you want to copy the backup from the old device to the new device.
+3. Remember the location you saved your backup to. For instance, you might want to save them to the *Download* folder.
+
+## 3. Import the backup (new device)
+1. Install Conversations on your new device.
+2. Open Conversations for the first time.
+3. Tap on "Use other server"
+4. Tap on the three dot menu in the upper right corner and tap on "Import backup"
+5. If your backup files are not listed, tap on the cloud symbol in the upper right corner to choose the files from the where you saved them.
+6. Enter your account password to decrypt the backup.
+7. Remember to activate your account (head back to "manage accounts", see step 1.2).
+8. Check if chats work.
+
+Once confirmed that the new device is running fine you can just uninstall the app from the old device.
+
+Note: The backup only contains your text chats and required encryption keys, all the files need to be transferred separately and put on the new device in the same locations.
+
+Done!
+
+## Further information / troubleshooting
+### Unable to decrypt
+This backup method will include your OMEMO keys. Due to forward secrecy you will not be able to recover messages sent and received between creating the backup and restoring it. If you have a server side archive (MAM) those messages will be retrieved but displayed as *unable to decrypt*. For technical reasons you might also lose the first message you either sent or receive after the restore; for each conversation you have. This message will then also show up as *unable to decrypt*, but this will automatically recover itself as long as both participants are on Conversations 2.3.11+. Note that this doesn’t happen if you just transfer to a new phone and no messages have been exchanged between backup and restore.
+
+In the vast, vast majority of cases you won’t have to manually delete OMEMO keys or do anything like that. Conversations only introduced the official backup feature in 2.4.0 after making sure the *OMEMO self healing* mechanism introduced in 2.3.11 works fine.
diff --git a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java
index a1b5f9e771cbbd1ce4e8706ea7ef36b091c9ae8c..c118d7375365febc8206b36bf4f2ec1ceb8fa98c 100644
--- a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java
+++ b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java
@@ -1,5 +1,7 @@
package eu.siacs.conversations.services;
+import static eu.siacs.conversations.utils.Compatibility.s;
+
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -304,7 +306,9 @@ public class ImportBackupService extends Service {
mBuilder.setContentTitle(getString(R.string.notification_restored_backup_title))
.setContentText(getString(R.string.notification_restored_backup_subtitle))
.setAutoCancel(true)
- .setContentIntent(PendingIntent.getActivity(this, 145, new Intent(this, ManageAccountActivity.class), PendingIntent.FLAG_UPDATE_CURRENT))
+ .setContentIntent(PendingIntent.getActivity(this, 145, new Intent(this, ManageAccountActivity.class), s()
+ ? PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
+ : PendingIntent.FLAG_UPDATE_CURRENT))
.setSmallIcon(R.drawable.ic_unarchive_white_24dp);
notificationManager.notify(NOTIFICATION_ID, mBuilder.build());
}
diff --git a/src/conversations/res/values-zh-rTW/strings.xml b/src/conversations/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..a5ba73d42d4d6ae97ce46021774d9f63b559e125
--- /dev/null
+++ b/src/conversations/res/values-zh-rTW/strings.xml
@@ -0,0 +1,8 @@
+
+
+ 挑選您的 XMPP 提供者
+ 使用 conversations.im
+ 建立新帳戶
+ 您的伺服器邀請
+ 分享邀請至…
+
\ No newline at end of file
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
index 3400f572efb8969d4347b1a380e908f8d7cc244f..3dcb34ec4fe14e4a6140f3b63430564e2dfd3c08 100644
--- a/src/main/AndroidManifest.xml
+++ b/src/main/AndroidManifest.xml
@@ -2,6 +2,7 @@
+
@@ -59,6 +60,10 @@
+
+
+
+
diff --git a/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java b/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java
index a676e5d5d32613f846522a937f0fe914e48eae55..db84e0cf4da5e3ca8336dcb6842f594acdc45cf1 100644
--- a/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java
+++ b/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java
@@ -207,8 +207,8 @@ public class PgpDecryptionService {
}
}
final String url = message.getFileParams().url;
- mXmppConnectionService.getFileBackend().updateFileParams(message, url);
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
+ mXmppConnectionService.getFileBackend().updateFileParams(message, url);
mXmppConnectionService.updateMessage(message);
if (!inputFile.delete()) {
Log.w(Config.LOGTAG,"unable to delete pgp encrypted source file "+inputFile.getAbsolutePath());
diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java
index f1bae9956a33ddc3994879fb059f69e044fe0f57..20db7bfbd31bb1f94cde0af1ac69fdbbb24fff82 100644
--- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java
+++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java
@@ -144,6 +144,7 @@ public class HttpUploadConnection implements Transferable, AbstractConnectionMan
@Override
public void onFailure(@NotNull final Throwable throwable) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to request slot", throwable);
+ // TODO consider fall back to jingle in 1-on-1 chats with exactly one online presence
fail(throwable.getMessage());
}
}, MoreExecutors.directExecutor());
diff --git a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java
index 000fa036d9675f7ce84f282740f3a005482dcfc2..f4b01b7d3199cf821732226b79cc1c998296ee89 100644
--- a/src/main/java/eu/siacs/conversations/parser/AbstractParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/AbstractParser.java
@@ -126,7 +126,7 @@ public abstract class AbstractParser {
return user;
}
- public static String extractErrorMessage(Element packet) {
+ public static String extractErrorMessage(final Element packet) {
final Element error = packet.findChild("error");
if (error != null && error.getChildren().size() > 0) {
final List errorNames = orderedElementNames(error.getChildren());
diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
index 186562420b214670f0ddf2b6159c6b2efc884991..843231f5c7e632ec475fbf95c9ef19499a9ad7ed 100644
--- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java
@@ -196,8 +196,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
}
private void parseEvent(final Element event, final Jid from, final Account account) {
- Element items = event.findChild("items");
- String node = items == null ? null : items.getAttribute("node");
+ final Element items = event.findChild("items");
+ final String node = items == null ? null : items.getAttribute("node");
if ("urn:xmpp:avatar:metadata".equals(node)) {
Avatar avatar = Avatar.parseMetadata(items);
if (avatar != null) {
@@ -327,7 +327,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
}
if (id.startsWith(JingleRtpConnection.JINGLE_MESSAGE_PROCEED_ID_PREFIX)) {
final String sessionId = id.substring(JingleRtpConnection.JINGLE_MESSAGE_PROCEED_ID_PREFIX.length());
- mXmppConnectionService.getJingleConnectionManager().failProceed(account, from, sessionId);
+ final String message = extractErrorMessage(packet);
+ mXmppConnectionService.getJingleConnectionManager().failProceed(account, from, sessionId, message);
return true;
}
mXmppConnectionService.markMessage(account,
@@ -1001,7 +1002,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
}
final Element event = original.findChild("event", "http://jabber.org/protocol/pubsub#event");
- if (event != null && InvalidJid.hasValidFrom(original)) {
+ if (event != null && InvalidJid.hasValidFrom(original) && original.getFrom().isBareJid()) {
if (event.hasChild("items")) {
parseEvent(event, original.getFrom(), account);
} else if (event.hasChild("delete")) {
@@ -1013,6 +1014,9 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
final String nick = packet.findChildContent("nick", Namespace.NICK);
if (nick != null && InvalidJid.hasValidFrom(original)) {
+ if (mXmppConnectionService.isMuc(account, from)) {
+ return;
+ }
final Contact contact = account.getRoster().getContact(from);
if (contact.setPresenceName(nick)) {
mXmppConnectionService.syncRoster(account);
diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
index d5c9dd23b3e57c7016a6c1d22b7f2f0c577e9dda..9612f69a9fb831d7e6f72eff41784224f928527e 100644
--- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
+++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
@@ -70,7 +70,6 @@ import eu.siacs.conversations.services.AttachFileToConversationRunnable;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.ui.adapter.MediaAdapter;
import eu.siacs.conversations.ui.util.Attachment;
-import eu.siacs.conversations.utils.Compatibility;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.FileUtils;
import eu.siacs.conversations.utils.FileWriterException;
@@ -404,25 +403,23 @@ public class FileBackend {
public static Uri getMediaUri(Context context, File file) {
final String filePath = file.getAbsolutePath();
- final Cursor cursor;
- try {
- cursor =
- context.getContentResolver()
- .query(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
- new String[] {MediaStore.Images.Media._ID},
- MediaStore.Images.Media.DATA + "=? ",
- new String[] {filePath},
- null);
- } catch (SecurityException e) {
- return null;
- }
- if (cursor != null && cursor.moveToFirst()) {
- final int id = cursor.getInt(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
- cursor.close();
- return Uri.withAppendedPath(
- MediaStore.Images.Media.EXTERNAL_CONTENT_URI, String.valueOf(id));
- } else {
+ try (final Cursor cursor =
+ context.getContentResolver()
+ .query(
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
+ new String[] {MediaStore.Images.Media._ID},
+ MediaStore.Images.Media.DATA + "=? ",
+ new String[] {filePath},
+ null)) {
+ if (cursor != null && cursor.moveToFirst()) {
+ final int id =
+ cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID));
+ return Uri.withAppendedPath(
+ MediaStore.Images.Media.EXTERNAL_CONTENT_URI, String.valueOf(id));
+ } else {
+ return null;
+ }
+ } catch (final Exception e) {
return null;
}
}
@@ -1533,43 +1530,69 @@ public class FileBackend {
updateFileParams(message, null);
}
- public void updateFileParams(Message message, String url) {
- DownloadableFile file = getFile(message);
+ public void updateFileParams(final Message message, final String url) {
+ final boolean encrypted =
+ message.getEncryption() == Message.ENCRYPTION_PGP
+ || message.getEncryption() == Message.ENCRYPTION_DECRYPTED;
+ final DownloadableFile file = getFile(message);
final String mime = file.getMimeType();
final boolean privateMessage = message.isPrivateMessage();
final boolean image =
message.getType() == Message.TYPE_IMAGE
|| (mime != null && mime.startsWith("image/"));
- final boolean video = mime != null && mime.startsWith("video/");
- final boolean audio = mime != null && mime.startsWith("audio/");
- final boolean pdf = "application/pdf".equals(mime);
Message.FileParams fileParams = new Message.FileParams();
if (url != null) {
fileParams.url = url;
}
- fileParams.size = file.getSize();
- if (image || video || pdf) {
- try {
- final Dimensions dimensions;
- if (video) {
- dimensions = getVideoDimensions(file);
- } else if (pdf) {
- dimensions = getPdfDocumentDimensions(file);
- } else {
- dimensions = getImageDimensions(file);
+ if (encrypted && !file.exists()) {
+ Log.d(Config.LOGTAG, "skipping updateFileParams because file is encrypted");
+ final DownloadableFile encryptedFile = getFile(message, false);
+ fileParams.size = encryptedFile.getSize();
+ } else {
+ Log.d(Config.LOGTAG, "running updateFileParams");
+ final boolean ambiguous = MimeUtils.AMBIGUOUS_CONTAINER_FORMATS.contains(mime);
+ final boolean video = mime != null && mime.startsWith("video/");
+ final boolean audio = mime != null && mime.startsWith("audio/");
+ final boolean pdf = "application/pdf".equals(mime);
+ fileParams.size = file.getSize();
+ if (ambiguous) {
+ try {
+ final Dimensions dimensions = getVideoDimensions(file);
+ if (dimensions.valid()) {
+ Log.d(Config.LOGTAG, "ambiguous file " + mime + " is video");
+ fileParams.width = dimensions.width;
+ fileParams.height = dimensions.height;
+ } else {
+ Log.d(Config.LOGTAG, "ambiguous file " + mime + " is audio");
+ fileParams.runtime = getMediaRuntime(file);
+ }
+ } catch (final NotAVideoFile e) {
+ Log.d(Config.LOGTAG, "ambiguous file " + mime + " is audio");
+ fileParams.runtime = getMediaRuntime(file);
}
- if (dimensions.valid()) {
- fileParams.width = dimensions.width;
- fileParams.height = dimensions.height;
+ } else if (image || video || pdf) {
+ try {
+ final Dimensions dimensions;
+ if (video) {
+ dimensions = getVideoDimensions(file);
+ } else if (pdf) {
+ dimensions = getPdfDocumentDimensions(file);
+ } else {
+ dimensions = getImageDimensions(file);
+ }
+ if (dimensions.valid()) {
+ fileParams.width = dimensions.width;
+ fileParams.height = dimensions.height;
+ }
+ } catch (NotAVideoFile notAVideoFile) {
+ Log.d(
+ Config.LOGTAG,
+ "file with mime type " + file.getMimeType() + " was not a video file");
+ // fall threw
}
- } catch (NotAVideoFile notAVideoFile) {
- Log.d(
- Config.LOGTAG,
- "file with mime type " + file.getMimeType() + " was not a video file");
- // fall threw
+ } else if (audio) {
+ fileParams.runtime = getMediaRuntime(file);
}
- } else if (audio) {
- fileParams.runtime = getMediaRuntime(file);
}
message.setFileParams(fileParams);
message.setDeleted(false);
@@ -1579,14 +1602,18 @@ public class FileBackend {
: (image ? Message.TYPE_IMAGE : Message.TYPE_FILE));
}
- private int getMediaRuntime(File file) {
+ private int getMediaRuntime(final File file) {
try {
- MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
+ final MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
mediaMetadataRetriever.setDataSource(file.toString());
- return Integer.parseInt(
+ final String value =
mediaMetadataRetriever.extractMetadata(
- MediaMetadataRetriever.METADATA_KEY_DURATION));
- } catch (RuntimeException e) {
+ MediaMetadataRetriever.METADATA_KEY_DURATION);
+ if (Strings.isNullOrEmpty(value)) {
+ return 0;
+ }
+ return Integer.parseInt(value);
+ } catch (final IllegalArgumentException e) {
return 0;
}
}
diff --git a/src/main/java/eu/siacs/conversations/services/AppRTCBluetoothManager.java b/src/main/java/eu/siacs/conversations/services/AppRTCBluetoothManager.java
index 862cdf0c7f007aec351550ddd26149dacd75a1b1..484072605174ab852628cf501086145d9a14dddf 100644
--- a/src/main/java/eu/siacs/conversations/services/AppRTCBluetoothManager.java
+++ b/src/main/java/eu/siacs/conversations/services/AppRTCBluetoothManager.java
@@ -9,6 +9,7 @@
*/
package eu.siacs.conversations.services;
+import android.Manifest;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
@@ -20,25 +21,25 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.media.AudioManager;
+import android.os.Build;
import android.os.Handler;
import android.os.Looper;
-import android.os.Process;
import android.util.Log;
import androidx.annotation.Nullable;
+import androidx.core.app.ActivityCompat;
+
+import com.google.common.collect.ImmutableList;
import org.webrtc.ThreadUtils;
+import java.util.Collections;
import java.util.List;
-import java.util.Set;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.utils.AppRTCUtils;
-/**
- * AppRTCProximitySensor manages functions related to Bluetoth devices in the
- * AppRTC demo.
- */
+/** AppRTCProximitySensor manages functions related to Bluetoth devices in the AppRTC demo. */
public class AppRTCBluetoothManager {
// Timeout interval for starting or stopping audio to a Bluetooth SCO device.
private static final int BLUETOOTH_SCO_TIMEOUT_MS = 4000;
@@ -46,28 +47,26 @@ public class AppRTCBluetoothManager {
private static final int MAX_SCO_CONNECTION_ATTEMPTS = 2;
private final Context apprtcContext;
private final AppRTCAudioManager apprtcAudioManager;
- @Nullable
- private final AudioManager audioManager;
+ @Nullable private final AudioManager audioManager;
private final Handler handler;
private final BluetoothProfile.ServiceListener bluetoothServiceListener;
private final BroadcastReceiver bluetoothHeadsetReceiver;
int scoConnectionAttempts;
private State bluetoothState;
- @Nullable
- private BluetoothAdapter bluetoothAdapter;
- @Nullable
- private BluetoothHeadset bluetoothHeadset;
- @Nullable
- private BluetoothDevice bluetoothDevice;
+ @Nullable private BluetoothAdapter bluetoothAdapter;
+ @Nullable private BluetoothHeadset bluetoothHeadset;
+ @Nullable private BluetoothDevice bluetoothDevice;
// Runs when the Bluetooth timeout expires. We use that timeout after calling
// startScoAudio() or stopScoAudio() because we're not guaranteed to get a
// callback after those calls.
- private final Runnable bluetoothTimeoutRunnable = new Runnable() {
- @Override
- public void run() {
- bluetoothTimeout();
- }
- };
+ private final Runnable bluetoothTimeoutRunnable =
+ new Runnable() {
+ @Override
+ public void run() {
+ bluetoothTimeout();
+ }
+ };
+
protected AppRTCBluetoothManager(Context context, AppRTCAudioManager audioManager) {
Log.d(Config.LOGTAG, "ctor");
ThreadUtils.checkIsOnMainThread();
@@ -80,42 +79,29 @@ public class AppRTCBluetoothManager {
handler = new Handler(Looper.getMainLooper());
}
- /**
- * Construction.
- */
+ /** Construction. */
static AppRTCBluetoothManager create(Context context, AppRTCAudioManager audioManager) {
Log.d(Config.LOGTAG, "create" + AppRTCUtils.getThreadInfo());
return new AppRTCBluetoothManager(context, audioManager);
}
- /**
- * Returns the internal state.
- */
+ /** Returns the internal state. */
public State getState() {
ThreadUtils.checkIsOnMainThread();
return bluetoothState;
}
/**
- * Activates components required to detect Bluetooth devices and to enable
- * BT SCO (audio is routed via BT SCO) for the headset profile. The end
- * state will be HEADSET_UNAVAILABLE but a state machine has started which
- * will start a state change sequence where the final outcome depends on
- * if/when the BT headset is enabled.
- * Example of state change sequence when start() is called while BT device
- * is connected and enabled:
- * UNINITIALIZED --> HEADSET_UNAVAILABLE --> HEADSET_AVAILABLE -->
- * SCO_CONNECTING --> SCO_CONNECTED <==> audio is now routed via BT SCO.
- * Note that the AppRTCAudioManager is also involved in driving this state
- * change.
+ * Activates components required to detect Bluetooth devices and to enable BT SCO (audio is
+ * routed via BT SCO) for the headset profile. The end state will be HEADSET_UNAVAILABLE but a
+ * state machine has started which will start a state change sequence where the final outcome
+ * depends on if/when the BT headset is enabled. Example of state change sequence when start()
+ * is called while BT device is connected and enabled: UNINITIALIZED --> HEADSET_UNAVAILABLE -->
+ * HEADSET_AVAILABLE --> SCO_CONNECTING --> SCO_CONNECTED <==> audio is now routed via BT SCO.
+ * Note that the AppRTCAudioManager is also involved in driving this state change.
*/
public void start() {
ThreadUtils.checkIsOnMainThread();
- Log.d(Config.LOGTAG, "start");
- if (!hasPermission(apprtcContext, android.Manifest.permission.BLUETOOTH)) {
- Log.w(Config.LOGTAG, "Process (pid=" + Process.myPid() + ") lacks BLUETOOTH permission");
- return;
- }
if (bluetoothState != State.UNINITIALIZED) {
Log.w(Config.LOGTAG, "Invalid BT state");
return;
@@ -130,11 +116,10 @@ public class AppRTCBluetoothManager {
return;
}
// Ensure that the device supports use of BT SCO audio for off call use cases.
- if (!audioManager.isBluetoothScoAvailableOffCall()) {
+ if (this.audioManager == null || !audioManager.isBluetoothScoAvailableOffCall()) {
Log.e(Config.LOGTAG, "Bluetooth SCO audio is not available off call");
return;
}
- logBluetoothAdapterInfo(bluetoothAdapter);
// Establish a connection to the HEADSET profile (includes both Bluetooth Headset and
// Hands-Free) proxy object and install a listener.
if (!getBluetoothProfileProxy(
@@ -149,16 +134,20 @@ public class AppRTCBluetoothManager {
// Register receiver for change in audio connection state of the Headset profile.
bluetoothHeadsetFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
registerReceiver(bluetoothHeadsetReceiver, bluetoothHeadsetFilter);
- Log.d(Config.LOGTAG, "HEADSET profile state: "
- + stateToString(bluetoothAdapter.getProfileConnectionState(BluetoothProfile.HEADSET)));
+ if (hasBluetoothConnectPermission()) {
+ Log.d(
+ Config.LOGTAG,
+ "HEADSET profile state: "
+ + stateToString(
+ bluetoothAdapter.getProfileConnectionState(
+ BluetoothProfile.HEADSET)));
+ }
Log.d(Config.LOGTAG, "Bluetooth proxy for headset profile has started");
bluetoothState = State.HEADSET_UNAVAILABLE;
Log.d(Config.LOGTAG, "start done: BT state=" + bluetoothState);
}
- /**
- * Stops and closes all components related to Bluetooth audio.
- */
+ /** Stops and closes all components related to Bluetooth audio. */
public void stop() {
ThreadUtils.checkIsOnMainThread();
Log.d(Config.LOGTAG, "stop: BT state=" + bluetoothState);
@@ -184,23 +173,29 @@ public class AppRTCBluetoothManager {
}
/**
- * Starts Bluetooth SCO connection with remote device.
- * Note that the phone application always has the priority on the usage of the SCO connection
- * for telephony. If this method is called while the phone is in call it will be ignored.
- * Similarly, if a call is received or sent while an application is using the SCO connection,
- * the connection will be lost for the application and NOT returned automatically when the call
- * ends. Also note that: up to and including API version JELLY_BEAN_MR1, this method initiates a
- * virtual voice call to the Bluetooth headset. After API version JELLY_BEAN_MR2 only a raw SCO
- * audio connection is established.
+ * Starts Bluetooth SCO connection with remote device. Note that the phone application always
+ * has the priority on the usage of the SCO connection for telephony. If this method is called
+ * while the phone is in call it will be ignored. Similarly, if a call is received or sent while
+ * an application is using the SCO connection, the connection will be lost for the application
+ * and NOT returned automatically when the call ends. Also note that: up to and including API
+ * version JELLY_BEAN_MR1, this method initiates a virtual voice call to the Bluetooth headset.
+ * After API version JELLY_BEAN_MR2 only a raw SCO audio connection is established.
* TODO(henrika): should we add support for virtual voice call to BT headset also for JBMR2 and
* higher. It might be required to initiates a virtual voice call since many devices do not
* accept SCO audio without a "call".
*/
public boolean startScoAudio() {
ThreadUtils.checkIsOnMainThread();
- Log.d(Config.LOGTAG, "startSco: BT state=" + bluetoothState + ", "
- + "attempts: " + scoConnectionAttempts + ", "
- + "SCO is on: " + isScoOn());
+ Log.d(
+ Config.LOGTAG,
+ "startSco: BT state="
+ + bluetoothState
+ + ", "
+ + "attempts: "
+ + scoConnectionAttempts
+ + ", "
+ + "SCO is on: "
+ + isScoOn());
if (scoConnectionAttempts >= MAX_SCO_CONNECTION_ATTEMPTS) {
Log.e(Config.LOGTAG, "BT SCO connection fails - no more attempts");
return false;
@@ -213,24 +208,29 @@ public class AppRTCBluetoothManager {
Log.d(Config.LOGTAG, "Starting Bluetooth SCO and waits for ACTION_AUDIO_STATE_CHANGED...");
// The SCO connection establishment can take several seconds, hence we cannot rely on the
// connection to be available when the method returns but instead register to receive the
- // intent ACTION_SCO_AUDIO_STATE_UPDATED and wait for the state to be SCO_AUDIO_STATE_CONNECTED.
+ // intent ACTION_SCO_AUDIO_STATE_UPDATED and wait for the state to be
+ // SCO_AUDIO_STATE_CONNECTED.
bluetoothState = State.SCO_CONNECTING;
audioManager.startBluetoothSco();
audioManager.setBluetoothScoOn(true);
scoConnectionAttempts++;
startTimer();
- Log.d(Config.LOGTAG, "startScoAudio done: BT state=" + bluetoothState + ", "
- + "SCO is on: " + isScoOn());
+ Log.d(
+ Config.LOGTAG,
+ "startScoAudio done: BT state="
+ + bluetoothState
+ + ", "
+ + "SCO is on: "
+ + isScoOn());
return true;
}
- /**
- * Stops Bluetooth SCO connection with remote device.
- */
+ /** Stops Bluetooth SCO connection with remote device. */
public void stopScoAudio() {
ThreadUtils.checkIsOnMainThread();
- Log.d(Config.LOGTAG, "stopScoAudio: BT state=" + bluetoothState + ", "
- + "SCO is on: " + isScoOn());
+ Log.d(
+ Config.LOGTAG,
+ "stopScoAudio: BT state=" + bluetoothState + ", " + "SCO is on: " + isScoOn());
if (bluetoothState != State.SCO_CONNECTING && bluetoothState != State.SCO_CONNECTED) {
return;
}
@@ -238,17 +238,18 @@ public class AppRTCBluetoothManager {
audioManager.stopBluetoothSco();
audioManager.setBluetoothScoOn(false);
bluetoothState = State.SCO_DISCONNECTING;
- Log.d(Config.LOGTAG, "stopScoAudio done: BT state=" + bluetoothState + ", "
- + "SCO is on: " + isScoOn());
+ Log.d(
+ Config.LOGTAG,
+ "stopScoAudio done: BT state=" + bluetoothState + ", " + "SCO is on: " + isScoOn());
}
/**
- * Use the BluetoothHeadset proxy object (controls the Bluetooth Headset
- * Service via IPC) to update the list of connected devices for the HEADSET
- * profile. The internal state will change to HEADSET_UNAVAILABLE or to
- * HEADSET_AVAILABLE and |bluetoothDevice| will be mapped to the connected
- * device if available.
+ * Use the BluetoothHeadset proxy object (controls the Bluetooth Headset Service via IPC) to
+ * update the list of connected devices for the HEADSET profile. The internal state will change
+ * to HEADSET_UNAVAILABLE or to HEADSET_AVAILABLE and |bluetoothDevice| will be mapped to the
+ * connected device if available.
*/
+ @SuppressLint("MissingPermission")
public void updateDevice() {
if (bluetoothState == State.UNINITIALIZED || bluetoothHeadset == null) {
return;
@@ -257,7 +258,12 @@ public class AppRTCBluetoothManager {
// Get connected devices for the headset profile. Returns the set of
// devices which are in state STATE_CONNECTED. The BluetoothDevice class
// is just a thin wrapper for a Bluetooth hardware address.
- List devices = bluetoothHeadset.getConnectedDevices();
+ final List devices;
+ if (hasBluetoothConnectPermission()) {
+ devices = bluetoothHeadset.getConnectedDevices();
+ } else {
+ devices = ImmutableList.of();
+ }
if (devices.isEmpty()) {
bluetoothDevice = null;
bluetoothState = State.HEADSET_UNAVAILABLE;
@@ -266,17 +272,21 @@ public class AppRTCBluetoothManager {
// Always use first device in list. Android only supports one device.
bluetoothDevice = devices.get(0);
bluetoothState = State.HEADSET_AVAILABLE;
- Log.d(Config.LOGTAG, "Connected bluetooth headset: "
- + "name=" + bluetoothDevice.getName() + ", "
- + "state=" + stateToString(bluetoothHeadset.getConnectionState(bluetoothDevice))
- + ", SCO audio=" + bluetoothHeadset.isAudioConnected(bluetoothDevice));
+ Log.d(
+ Config.LOGTAG,
+ "Connected bluetooth headset: "
+ + "name="
+ + bluetoothDevice.getName()
+ + ", "
+ + "state="
+ + stateToString(bluetoothHeadset.getConnectionState(bluetoothDevice))
+ + ", SCO audio="
+ + bluetoothHeadset.isAudioConnected(bluetoothDevice));
}
Log.d(Config.LOGTAG, "updateDevice done: BT state=" + bluetoothState);
}
- /**
- * Stubs for test mocks.
- */
+ /** Stubs for test mocks. */
@Nullable
protected AudioManager getAudioManager(Context context) {
return (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
@@ -295,52 +305,31 @@ public class AppRTCBluetoothManager {
return bluetoothAdapter.getProfileProxy(context, listener, profile);
}
- protected boolean hasPermission(Context context, String permission) {
- return apprtcContext.checkPermission(permission, Process.myPid(), Process.myUid())
- == PackageManager.PERMISSION_GRANTED;
- }
-
- /**
- * Logs the state of the local Bluetooth adapter.
- */
- @SuppressLint("HardwareIds")
- protected void logBluetoothAdapterInfo(BluetoothAdapter localAdapter) {
- Log.d(Config.LOGTAG, "BluetoothAdapter: "
- + "enabled=" + localAdapter.isEnabled() + ", "
- + "state=" + stateToString(localAdapter.getState()) + ", "
- + "name=" + localAdapter.getName() + ", "
- + "address=" + localAdapter.getAddress());
- // Log the set of BluetoothDevice objects that are bonded (paired) to the local adapter.
- Set pairedDevices = localAdapter.getBondedDevices();
- if (!pairedDevices.isEmpty()) {
- Log.d(Config.LOGTAG, "paired devices:");
- for (BluetoothDevice device : pairedDevices) {
- Log.d(Config.LOGTAG, " name=" + device.getName() + ", address=" + device.getAddress());
- }
+ protected boolean hasBluetoothConnectPermission() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ return ActivityCompat.checkSelfPermission(
+ apprtcContext, Manifest.permission.BLUETOOTH_CONNECT)
+ == PackageManager.PERMISSION_GRANTED;
+ } else {
+ return true;
}
}
- /**
- * Ensures that the audio manager updates its list of available audio devices.
- */
+ /** Ensures that the audio manager updates its list of available audio devices. */
private void updateAudioDeviceState() {
ThreadUtils.checkIsOnMainThread();
Log.d(Config.LOGTAG, "updateAudioDeviceState");
apprtcAudioManager.updateAudioDeviceState();
}
- /**
- * Starts timer which times out after BLUETOOTH_SCO_TIMEOUT_MS milliseconds.
- */
+ /** Starts timer which times out after BLUETOOTH_SCO_TIMEOUT_MS milliseconds. */
private void startTimer() {
ThreadUtils.checkIsOnMainThread();
Log.d(Config.LOGTAG, "startTimer");
handler.postDelayed(bluetoothTimeoutRunnable, BLUETOOTH_SCO_TIMEOUT_MS);
}
- /**
- * Cancels any outstanding timer tasks.
- */
+ /** Cancels any outstanding timer tasks. */
private void cancelTimer() {
ThreadUtils.checkIsOnMainThread();
Log.d(Config.LOGTAG, "cancelTimer");
@@ -348,23 +337,36 @@ public class AppRTCBluetoothManager {
}
/**
- * Called when start of the BT SCO channel takes too long time. Usually
- * happens when the BT device has been turned on during an ongoing call.
+ * Called when start of the BT SCO channel takes too long time. Usually happens when the BT
+ * device has been turned on during an ongoing call.
*/
+ @SuppressLint("MissingPermission")
private void bluetoothTimeout() {
ThreadUtils.checkIsOnMainThread();
if (bluetoothState == State.UNINITIALIZED || bluetoothHeadset == null) {
return;
}
- Log.d(Config.LOGTAG, "bluetoothTimeout: BT state=" + bluetoothState + ", "
- + "attempts: " + scoConnectionAttempts + ", "
- + "SCO is on: " + isScoOn());
+ Log.d(
+ Config.LOGTAG,
+ "bluetoothTimeout: BT state="
+ + bluetoothState
+ + ", "
+ + "attempts: "
+ + scoConnectionAttempts
+ + ", "
+ + "SCO is on: "
+ + isScoOn());
if (bluetoothState != State.SCO_CONNECTING) {
return;
}
// Bluetooth SCO should be connecting; check the latest result.
boolean scoConnected = false;
- List devices = bluetoothHeadset.getConnectedDevices();
+ final List devices;
+ if (hasBluetoothConnectPermission()) {
+ devices = bluetoothHeadset.getConnectedDevices();
+ } else {
+ devices = Collections.emptyList();
+ }
if (devices.size() > 0) {
bluetoothDevice = devices.get(0);
if (bluetoothHeadset.isAudioConnected(bluetoothDevice)) {
@@ -387,16 +389,12 @@ public class AppRTCBluetoothManager {
Log.d(Config.LOGTAG, "bluetoothTimeout done: BT state=" + bluetoothState);
}
- /**
- * Checks whether audio uses Bluetooth SCO.
- */
+ /** Checks whether audio uses Bluetooth SCO. */
private boolean isScoOn() {
return audioManager.isBluetoothScoOn();
}
- /**
- * Converts BluetoothAdapter states into local string representations.
- */
+ /** Converts BluetoothAdapter states into local string representations. */
private String stateToString(int state) {
switch (state) {
case BluetoothAdapter.STATE_DISCONNECTED:
@@ -412,11 +410,13 @@ public class AppRTCBluetoothManager {
case BluetoothAdapter.STATE_ON:
return "ON";
case BluetoothAdapter.STATE_TURNING_OFF:
- // Indicates the local Bluetooth adapter is turning off. Local clients should immediately
+ // Indicates the local Bluetooth adapter is turning off. Local clients should
+ // immediately
// attempt graceful disconnection of any remote links.
return "TURNING_OFF";
case BluetoothAdapter.STATE_TURNING_ON:
- // Indicates the local Bluetooth adapter is turning on. However local clients should wait
+ // Indicates the local Bluetooth adapter is turning on. However local clients should
+ // wait
// for STATE_ON before attempting to use the adapter.
return "TURNING_ON";
default:
@@ -457,7 +457,9 @@ public class AppRTCBluetoothManager {
if (profile != BluetoothProfile.HEADSET || bluetoothState == State.UNINITIALIZED) {
return;
}
- Log.d(Config.LOGTAG, "BluetoothServiceListener.onServiceConnected: BT state=" + bluetoothState);
+ Log.d(
+ Config.LOGTAG,
+ "BluetoothServiceListener.onServiceConnected: BT state=" + bluetoothState);
// Android only supports one connected Bluetooth Headset at a time.
bluetoothHeadset = (BluetoothHeadset) proxy;
updateAudioDeviceState();
@@ -470,7 +472,9 @@ public class AppRTCBluetoothManager {
if (profile != BluetoothProfile.HEADSET || bluetoothState == State.UNINITIALIZED) {
return;
}
- Log.d(Config.LOGTAG, "BluetoothServiceListener.onServiceDisconnected: BT state=" + bluetoothState);
+ Log.d(
+ Config.LOGTAG,
+ "BluetoothServiceListener.onServiceDisconnected: BT state=" + bluetoothState);
stopScoAudio();
bluetoothHeadset = null;
bluetoothDevice = null;
@@ -495,12 +499,20 @@ public class AppRTCBluetoothManager {
// headset while audio is active using another audio device.
if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
final int state =
- intent.getIntExtra(BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_DISCONNECTED);
- Log.d(Config.LOGTAG, "BluetoothHeadsetBroadcastReceiver.onReceive: "
- + "a=ACTION_CONNECTION_STATE_CHANGED, "
- + "s=" + stateToString(state) + ", "
- + "sb=" + isInitialStickyBroadcast() + ", "
- + "BT state: " + bluetoothState);
+ intent.getIntExtra(
+ BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_DISCONNECTED);
+ Log.d(
+ Config.LOGTAG,
+ "BluetoothHeadsetBroadcastReceiver.onReceive: "
+ + "a=ACTION_CONNECTION_STATE_CHANGED, "
+ + "s="
+ + stateToString(state)
+ + ", "
+ + "sb="
+ + isInitialStickyBroadcast()
+ + ", "
+ + "BT state: "
+ + bluetoothState);
if (state == BluetoothHeadset.STATE_CONNECTED) {
scoConnectionAttempts = 0;
updateAudioDeviceState();
@@ -516,13 +528,22 @@ public class AppRTCBluetoothManager {
// Change in the audio (SCO) connection state of the Headset profile.
// Typically received after call to startScoAudio() has finalized.
} else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
- final int state = intent.getIntExtra(
- BluetoothHeadset.EXTRA_STATE, BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
- Log.d(Config.LOGTAG, "BluetoothHeadsetBroadcastReceiver.onReceive: "
- + "a=ACTION_AUDIO_STATE_CHANGED, "
- + "s=" + stateToString(state) + ", "
- + "sb=" + isInitialStickyBroadcast() + ", "
- + "BT state: " + bluetoothState);
+ final int state =
+ intent.getIntExtra(
+ BluetoothHeadset.EXTRA_STATE,
+ BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+ Log.d(
+ Config.LOGTAG,
+ "BluetoothHeadsetBroadcastReceiver.onReceive: "
+ + "a=ACTION_AUDIO_STATE_CHANGED, "
+ + "s="
+ + stateToString(state)
+ + ", "
+ + "sb="
+ + isInitialStickyBroadcast()
+ + ", "
+ + "BT state: "
+ + bluetoothState);
if (state == BluetoothHeadset.STATE_AUDIO_CONNECTED) {
cancelTimer();
if (bluetoothState == State.SCO_CONNECTING) {
@@ -531,14 +552,18 @@ public class AppRTCBluetoothManager {
scoConnectionAttempts = 0;
updateAudioDeviceState();
} else {
- Log.w(Config.LOGTAG, "Unexpected state BluetoothHeadset.STATE_AUDIO_CONNECTED");
+ Log.w(
+ Config.LOGTAG,
+ "Unexpected state BluetoothHeadset.STATE_AUDIO_CONNECTED");
}
} else if (state == BluetoothHeadset.STATE_AUDIO_CONNECTING) {
Log.d(Config.LOGTAG, "+++ Bluetooth audio SCO is now connecting...");
} else if (state == BluetoothHeadset.STATE_AUDIO_DISCONNECTED) {
Log.d(Config.LOGTAG, "+++ Bluetooth audio SCO is now disconnected");
if (isInitialStickyBroadcast()) {
- Log.d(Config.LOGTAG, "Ignore STATE_AUDIO_DISCONNECTED initial sticky broadcast.");
+ Log.d(
+ Config.LOGTAG,
+ "Ignore STATE_AUDIO_DISCONNECTED initial sticky broadcast.");
return;
}
updateAudioDeviceState();
@@ -547,4 +572,4 @@ public class AppRTCBluetoothManager {
Log.d(Config.LOGTAG, "onReceive done: BT state=" + bluetoothState);
}
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/eu/siacs/conversations/services/ExportBackupService.java b/src/main/java/eu/siacs/conversations/services/ExportBackupService.java
index 2d9e9d58db5dccbe688b369e61bf65d40234c96a..376334fb6ec9c5e7c573e1b584c07f89acf6e578 100644
--- a/src/main/java/eu/siacs/conversations/services/ExportBackupService.java
+++ b/src/main/java/eu/siacs/conversations/services/ExportBackupService.java
@@ -1,5 +1,7 @@
package eu.siacs.conversations.services;
+import static eu.siacs.conversations.utils.Compatibility.s;
+
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
@@ -15,6 +17,7 @@ import android.util.Log;
import androidx.core.app.NotificationCompat;
+import com.google.common.base.CharMatcher;
import com.google.common.base.Strings;
import java.io.DataOutputStream;
@@ -114,7 +117,7 @@ public class ExportBackupService extends Service {
}
builder.append(intValue);
} else {
- DatabaseUtils.appendEscapedSQLString(builder, value);
+ appendEscapedSQLString(builder, value);
}
}
builder.append(")");
@@ -127,6 +130,10 @@ public class ExportBackupService extends Service {
writer.append(builder.toString());
}
+ private static void appendEscapedSQLString(final StringBuilder sb, final String sqlString) {
+ DatabaseUtils.appendEscapedSQLString(sb, CharMatcher.is('\u0000').removeFrom(sqlString));
+ }
+
private static void simpleExport(SQLiteDatabase db, String table, String column, String uuid, PrintWriter writer) {
final Cursor cursor = db.query(table, null, column + "=?", new String[]{uuid}, null, null, null);
while (cursor != null && cursor.moveToNext()) {
@@ -201,7 +208,7 @@ public class ExportBackupService extends Service {
} else if (value.matches("[0-9]+")) {
builder.append(value);
} else {
- DatabaseUtils.appendEscapedSQLString(builder, value);
+ appendEscapedSQLString(builder, value);
}
}
builder.append(")");
@@ -364,9 +371,11 @@ public class ExportBackupService extends Service {
PendingIntent openFolderIntent = null;
- for (Intent intent : getPossibleFileOpenIntents(this, path)) {
+ for (final Intent intent : getPossibleFileOpenIntents(this, path)) {
if (intent.resolveActivityInfo(getPackageManager(), 0) != null) {
- openFolderIntent = PendingIntent.getActivity(this, 189, intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ openFolderIntent = PendingIntent.getActivity(this, 189, intent, s()
+ ? PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
+ : PendingIntent.FLAG_UPDATE_CURRENT);
break;
}
}
@@ -382,7 +391,9 @@ public class ExportBackupService extends Service {
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setType(MIME_TYPE);
final Intent chooser = Intent.createChooser(intent, getString(R.string.share_backup_files));
- shareFilesIntent = PendingIntent.getActivity(this, 190, chooser, PendingIntent.FLAG_UPDATE_CURRENT);
+ shareFilesIntent = PendingIntent.getActivity(this, 190, chooser, s()
+ ? PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
+ : PendingIntent.FLAG_UPDATE_CURRENT);
}
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup");
diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java
index 3b5e7edaa12b12df5e39d299cb8153bae7db1c89..9477bf063b0d5ebe6fc769c48d156f2c42d7b205 100644
--- a/src/main/java/eu/siacs/conversations/services/NotificationService.java
+++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java
@@ -1784,7 +1784,9 @@ public class NotificationService {
mXmppConnectionService,
0,
new Intent(mXmppConnectionService, ConversationsActivity.class),
- 0);
+ s()
+ ? PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
+ : PendingIntent.FLAG_UPDATE_CURRENT);
} catch (RuntimeException e) {
return null;
}
@@ -1833,13 +1835,25 @@ public class NotificationService {
R.drawable.ic_play_circle_filled_white_48dp,
mXmppConnectionService.getString(R.string.start_orbot),
PendingIntent.getActivity(
- mXmppConnectionService, 147, TorServiceUtils.LAUNCH_INTENT, 0));
+ mXmppConnectionService,
+ 147,
+ TorServiceUtils.LAUNCH_INTENT,
+ s()
+ ? PendingIntent.FLAG_IMMUTABLE
+ | PendingIntent.FLAG_UPDATE_CURRENT
+ : PendingIntent.FLAG_UPDATE_CURRENT));
} else {
mBuilder.addAction(
R.drawable.ic_file_download_white_24dp,
mXmppConnectionService.getString(R.string.install_orbot),
PendingIntent.getActivity(
- mXmppConnectionService, 146, TorServiceUtils.INSTALL_INTENT, 0));
+ mXmppConnectionService,
+ 146,
+ TorServiceUtils.INSTALL_INTENT,
+ s()
+ ? PendingIntent.FLAG_IMMUTABLE
+ | PendingIntent.FLAG_UPDATE_CURRENT
+ : PendingIntent.FLAG_UPDATE_CURRENT));
}
}
mBuilder.setDeleteIntent(createDismissErrorIntent());
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
index a6d407496661c67e0e1c25e37c9c0df64ca91f92..7a4d6733b0b8f740761307114de51051651d604a 100644
--- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
@@ -1,5 +1,7 @@
package eu.siacs.conversations.services;
+import static eu.siacs.conversations.utils.Compatibility.s;
+
import android.Manifest;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
@@ -1423,7 +1425,9 @@ public class XmppConnectionService extends Service {
final Intent intent = new Intent(this, EventReceiver.class);
intent.setAction(ACTION_POST_CONNECTIVITY_CHANGE);
try {
- final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 1, intent, 0);
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 1, intent, s()
+ ? PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
+ : PendingIntent.FLAG_UPDATE_CURRENT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmManager.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, triggerAtMillis, pendingIntent);
} else {
@@ -1435,7 +1439,7 @@ public class XmppConnectionService extends Service {
}
public void scheduleWakeUpCall(int seconds, int requestCode) {
- final long timeToWake = SystemClock.elapsedRealtime() + (seconds < 0 ? 1 : seconds + 1) * 1000;
+ final long timeToWake = SystemClock.elapsedRealtime() + (seconds < 0 ? 1 : seconds + 1) * 1000L;
final AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
if (alarmManager == null) {
return;
@@ -1469,7 +1473,9 @@ public class XmppConnectionService extends Service {
final Intent intent = new Intent(this, EventReceiver.class);
intent.setAction(ACTION_IDLE_PING);
try {
- PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, 0);
+ final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, s()
+ ? PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
+ : PendingIntent.FLAG_UPDATE_CURRENT);
alarmManager.setAndAllowWhileIdle(AlarmManager.ELAPSED_REALTIME_WAKEUP, timeToWake, pendingIntent);
} catch (RuntimeException e) {
Log.d(Config.LOGTAG, "unable to schedule alarm for idle ping", e);
@@ -1482,7 +1488,7 @@ public class XmppConnectionService extends Service {
connection.setOnStatusChangedListener(this.statusListener);
connection.setOnPresencePacketReceivedListener(this.mPresenceParser);
connection.setOnUnregisteredIqPacketReceivedListener(this.mIqParser);
- connection.setOnJinglePacketReceivedListener(((a, jp) -> mJingleConnectionManager.deliverPacket(a, jp)));
+ connection.setOnJinglePacketReceivedListener((mJingleConnectionManager::deliverPacket));
connection.setOnBindListener(this.mOnBindListener);
connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener);
connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService);
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
index 481a216e4532c233fc7a86d70b4090d82de3acf6..c60557cceb9b8bb59365ea17db6c03ceddd5b774 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
@@ -1,5 +1,12 @@
package eu.siacs.conversations.ui;
+import static eu.siacs.conversations.ui.XmppActivity.EXTRA_ACCOUNT;
+import static eu.siacs.conversations.ui.XmppActivity.REQUEST_INVITE_TO_CONVERSATION;
+import static eu.siacs.conversations.ui.util.SoftKeyboardUtils.hideSoftKeyboard;
+import static eu.siacs.conversations.utils.PermissionUtils.allGranted;
+import static eu.siacs.conversations.utils.PermissionUtils.getFirstDenied;
+import static eu.siacs.conversations.utils.PermissionUtils.writeGranted;
+
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
@@ -55,6 +62,9 @@ import androidx.core.view.inputmethod.InputContentInfoCompat;
import androidx.databinding.DataBindingUtil;
import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableList;
+
+import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Arrays;
@@ -114,6 +124,7 @@ import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.utils.MessageUtils;
import eu.siacs.conversations.utils.NickValidityChecker;
import eu.siacs.conversations.utils.Patterns;
+import eu.siacs.conversations.utils.PermissionUtils;
import eu.siacs.conversations.utils.QuickLoader;
import eu.siacs.conversations.utils.StylingHelper;
import eu.siacs.conversations.utils.TimeFrameUtils;
@@ -129,18 +140,10 @@ import eu.siacs.conversations.xmpp.jingle.Media;
import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession;
import eu.siacs.conversations.xmpp.jingle.RtpCapability;
-import static eu.siacs.conversations.ui.XmppActivity.EXTRA_ACCOUNT;
-import static eu.siacs.conversations.ui.XmppActivity.REQUEST_INVITE_TO_CONVERSATION;
-import static eu.siacs.conversations.ui.util.SoftKeyboardUtils.hideSoftKeyboard;
-import static eu.siacs.conversations.utils.PermissionUtils.allGranted;
-import static eu.siacs.conversations.utils.PermissionUtils.getFirstDenied;
-import static eu.siacs.conversations.utils.PermissionUtils.writeGranted;
-
-import org.jetbrains.annotations.NotNull;
-
-
-public class ConversationFragment extends XmppFragment implements EditMessage.KeyboardListener, MessageAdapter.OnContactPictureLongClicked, MessageAdapter.OnContactPictureClicked {
-
+public class ConversationFragment extends XmppFragment
+ implements EditMessage.KeyboardListener,
+ MessageAdapter.OnContactPictureLongClicked,
+ MessageAdapter.OnContactPictureClicked {
public static final int REQUEST_SEND_MESSAGE = 0x0201;
public static final int REQUEST_DECRYPT_PGP = 0x0202;
@@ -161,10 +164,14 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
public static final int ATTACHMENT_CHOICE_RECORD_VIDEO = 0x0307;
public static final String RECENTLY_USED_QUICK_ACTION = "recently_used_quick_action";
- public static final String STATE_CONVERSATION_UUID = ConversationFragment.class.getName() + ".uuid";
- public static final String STATE_SCROLL_POSITION = ConversationFragment.class.getName() + ".scroll_position";
- public static final String STATE_PHOTO_URI = ConversationFragment.class.getName() + ".media_previews";
- public static final String STATE_MEDIA_PREVIEWS = ConversationFragment.class.getName() + ".take_photo_uri";
+ public static final String STATE_CONVERSATION_UUID =
+ ConversationFragment.class.getName() + ".uuid";
+ public static final String STATE_SCROLL_POSITION =
+ ConversationFragment.class.getName() + ".scroll_position";
+ public static final String STATE_PHOTO_URI =
+ ConversationFragment.class.getName() + ".media_previews";
+ public static final String STATE_MEDIA_PREVIEWS =
+ ConversationFragment.class.getName() + ".take_photo_uri";
private static final String STATE_LAST_MESSAGE_UUID = "state_last_message_uuid";
private final List messageList = new ArrayList<>();
@@ -185,282 +192,376 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
private Toast messageLoaderToast;
private ConversationsActivity activity;
private boolean reInitRequiredOnStart = true;
- private final OnClickListener clickToMuc = new OnClickListener() {
+ private final OnClickListener clickToMuc =
+ new OnClickListener() {
- @Override
- public void onClick(View v) {
- ConferenceDetailsActivity.open(getActivity(), conversation);
- }
- };
- private final OnClickListener leaveMuc = new OnClickListener() {
-
- @Override
- public void onClick(View v) {
- activity.xmppConnectionService.archiveConversation(conversation);
- }
- };
- private final OnClickListener joinMuc = new OnClickListener() {
-
- @Override
- public void onClick(View v) {
- activity.xmppConnectionService.joinMuc(conversation);
- }
- };
-
- private final OnClickListener acceptJoin = new OnClickListener() {
- @Override
- public void onClick(View v) {
- conversation.setAttribute("accept_non_anonymous", true);
- activity.xmppConnectionService.updateConversation(conversation);
- activity.xmppConnectionService.joinMuc(conversation);
- }
- };
+ @Override
+ public void onClick(View v) {
+ ConferenceDetailsActivity.open(getActivity(), conversation);
+ }
+ };
+ private final OnClickListener leaveMuc =
+ new OnClickListener() {
- private final OnClickListener enterPassword = new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ activity.xmppConnectionService.archiveConversation(conversation);
+ }
+ };
+ private final OnClickListener joinMuc =
+ new OnClickListener() {
- @Override
- public void onClick(View v) {
- MucOptions muc = conversation.getMucOptions();
- String password = muc.getPassword();
- if (password == null) {
- password = "";
- }
- activity.quickPasswordEdit(password, value -> {
- activity.xmppConnectionService.providePasswordForMuc(conversation, value);
- return null;
- });
- }
- };
- private final OnScrollListener mOnScrollListener = new OnScrollListener() {
+ @Override
+ public void onClick(View v) {
+ activity.xmppConnectionService.joinMuc(conversation);
+ }
+ };
+
+ private final OnClickListener acceptJoin =
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ conversation.setAttribute("accept_non_anonymous", true);
+ activity.xmppConnectionService.updateConversation(conversation);
+ activity.xmppConnectionService.joinMuc(conversation);
+ }
+ };
- @Override
- public void onScrollStateChanged(AbsListView view, int scrollState) {
- if (AbsListView.OnScrollListener.SCROLL_STATE_IDLE == scrollState) {
- fireReadEvent();
- }
- }
+ private final OnClickListener enterPassword =
+ new OnClickListener() {
- @Override
- public void onScroll(final AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
- toggleScrollDownButton(view);
- synchronized (ConversationFragment.this.messageList) {
- if (firstVisibleItem < 5 && conversation != null && conversation.messagesLoaded.compareAndSet(true, false) && messageList.size() > 0) {
- long timestamp;
- if (messageList.get(0).getType() == Message.TYPE_STATUS && messageList.size() >= 2) {
- timestamp = messageList.get(1).getTimeSent();
- } else {
- timestamp = messageList.get(0).getTimeSent();
+ @Override
+ public void onClick(View v) {
+ MucOptions muc = conversation.getMucOptions();
+ String password = muc.getPassword();
+ if (password == null) {
+ password = "";
}
- activity.xmppConnectionService.loadMoreMessages(conversation, timestamp, new XmppConnectionService.OnMoreMessagesLoaded() {
- @Override
- public void onMoreMessagesLoaded(final int c, final Conversation conversation) {
- if (ConversationFragment.this.conversation != conversation) {
- conversation.messagesLoaded.set(true);
- return;
- }
- runOnUiThread(() -> {
- synchronized (messageList) {
- final int oldPosition = binding.messagesView.getFirstVisiblePosition();
- Message message = null;
- int childPos;
- for (childPos = 0; childPos + oldPosition < messageList.size(); ++childPos) {
- message = messageList.get(oldPosition + childPos);
- if (message.getType() != Message.TYPE_STATUS) {
- break;
- }
- }
- final String uuid = message != null ? message.getUuid() : null;
- View v = binding.messagesView.getChildAt(childPos);
- final int pxOffset = (v == null) ? 0 : v.getTop();
- ConversationFragment.this.conversation.populateWithMessages(ConversationFragment.this.messageList);
- try {
- updateStatusMessages();
- } catch (IllegalStateException e) {
- Log.d(Config.LOGTAG, "caught illegal state exception while updating status messages");
- }
- messageListAdapter.notifyDataSetChanged();
- int pos = Math.max(getIndexOf(uuid, messageList), 0);
- binding.messagesView.setSelectionFromTop(pos, pxOffset);
- if (messageLoaderToast != null) {
- messageLoaderToast.cancel();
- }
- conversation.messagesLoaded.set(true);
- }
+ activity.quickPasswordEdit(
+ password,
+ value -> {
+ activity.xmppConnectionService.providePasswordForMuc(
+ conversation, value);
+ return null;
});
- }
-
- @Override
- public void informUser(final int resId) {
+ }
+ };
+ private final OnScrollListener mOnScrollListener =
+ new OnScrollListener() {
+
+ @Override
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ if (AbsListView.OnScrollListener.SCROLL_STATE_IDLE == scrollState) {
+ fireReadEvent();
+ }
+ }
- runOnUiThread(() -> {
- if (messageLoaderToast != null) {
- messageLoaderToast.cancel();
- }
- if (ConversationFragment.this.conversation != conversation) {
- return;
- }
- messageLoaderToast = Toast.makeText(view.getContext(), resId, Toast.LENGTH_LONG);
- messageLoaderToast.show();
- });
+ @Override
+ public void onScroll(
+ final AbsListView view,
+ int firstVisibleItem,
+ int visibleItemCount,
+ int totalItemCount) {
+ toggleScrollDownButton(view);
+ synchronized (ConversationFragment.this.messageList) {
+ if (firstVisibleItem < 5
+ && conversation != null
+ && conversation.messagesLoaded.compareAndSet(true, false)
+ && messageList.size() > 0) {
+ long timestamp;
+ if (messageList.get(0).getType() == Message.TYPE_STATUS
+ && messageList.size() >= 2) {
+ timestamp = messageList.get(1).getTimeSent();
+ } else {
+ timestamp = messageList.get(0).getTimeSent();
+ }
+ activity.xmppConnectionService.loadMoreMessages(
+ conversation,
+ timestamp,
+ new XmppConnectionService.OnMoreMessagesLoaded() {
+ @Override
+ public void onMoreMessagesLoaded(
+ final int c, final Conversation conversation) {
+ if (ConversationFragment.this.conversation
+ != conversation) {
+ conversation.messagesLoaded.set(true);
+ return;
+ }
+ runOnUiThread(
+ () -> {
+ synchronized (messageList) {
+ final int oldPosition =
+ binding.messagesView
+ .getFirstVisiblePosition();
+ Message message = null;
+ int childPos;
+ for (childPos = 0;
+ childPos + oldPosition
+ < messageList.size();
+ ++childPos) {
+ message =
+ messageList.get(
+ oldPosition
+ + childPos);
+ if (message.getType()
+ != Message.TYPE_STATUS) {
+ break;
+ }
+ }
+ final String uuid =
+ message != null
+ ? message.getUuid()
+ : null;
+ View v =
+ binding.messagesView.getChildAt(
+ childPos);
+ final int pxOffset =
+ (v == null) ? 0 : v.getTop();
+ ConversationFragment.this.conversation
+ .populateWithMessages(
+ ConversationFragment
+ .this
+ .messageList);
+ try {
+ updateStatusMessages();
+ } catch (IllegalStateException e) {
+ Log.d(
+ Config.LOGTAG,
+ "caught illegal state exception while updating status messages");
+ }
+ messageListAdapter
+ .notifyDataSetChanged();
+ int pos =
+ Math.max(
+ getIndexOf(
+ uuid,
+ messageList),
+ 0);
+ binding.messagesView
+ .setSelectionFromTop(
+ pos, pxOffset);
+ if (messageLoaderToast != null) {
+ messageLoaderToast.cancel();
+ }
+ conversation.messagesLoaded.set(true);
+ }
+ });
+ }
+ @Override
+ public void informUser(final int resId) {
+
+ runOnUiThread(
+ () -> {
+ if (messageLoaderToast != null) {
+ messageLoaderToast.cancel();
+ }
+ if (ConversationFragment.this.conversation
+ != conversation) {
+ return;
+ }
+ messageLoaderToast =
+ Toast.makeText(
+ view.getContext(),
+ resId,
+ Toast.LENGTH_LONG);
+ messageLoaderToast.show();
+ });
+ }
+ });
}
- });
-
+ }
}
- }
- }
- };
- private final EditMessage.OnCommitContentListener mEditorContentListener = new EditMessage.OnCommitContentListener() {
- @Override
- public boolean onCommitContent(InputContentInfoCompat inputContentInfo, int flags, Bundle opts, String[] contentMimeTypes) {
- // try to get permission to read the image, if applicable
- if ((flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION) != 0) {
- try {
- inputContentInfo.requestPermission();
- } catch (Exception e) {
- Log.e(Config.LOGTAG, "InputContentInfoCompat#requestPermission() failed.", e);
- Toast.makeText(getActivity(), activity.getString(R.string.no_permission_to_access_x, inputContentInfo.getDescription()), Toast.LENGTH_LONG
- ).show();
- return false;
+ };
+ private final EditMessage.OnCommitContentListener mEditorContentListener =
+ new EditMessage.OnCommitContentListener() {
+ @Override
+ public boolean onCommitContent(
+ InputContentInfoCompat inputContentInfo,
+ int flags,
+ Bundle opts,
+ String[] contentMimeTypes) {
+ // try to get permission to read the image, if applicable
+ if ((flags & InputConnectionCompat.INPUT_CONTENT_GRANT_READ_URI_PERMISSION)
+ != 0) {
+ try {
+ inputContentInfo.requestPermission();
+ } catch (Exception e) {
+ Log.e(
+ Config.LOGTAG,
+ "InputContentInfoCompat#requestPermission() failed.",
+ e);
+ Toast.makeText(
+ getActivity(),
+ activity.getString(
+ R.string.no_permission_to_access_x,
+ inputContentInfo.getDescription()),
+ Toast.LENGTH_LONG)
+ .show();
+ return false;
+ }
+ }
+ if (hasPermissions(
+ REQUEST_ADD_EDITOR_CONTENT,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+ attachEditorContentToConversation(inputContentInfo.getContentUri());
+ } else {
+ mPendingEditorContent = inputContentInfo.getContentUri();
+ }
+ return true;
}
- }
- if (hasPermissions(REQUEST_ADD_EDITOR_CONTENT, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
- attachEditorContentToConversation(inputContentInfo.getContentUri());
- } else {
- mPendingEditorContent = inputContentInfo.getContentUri();
- }
- return true;
- }
- };
+ };
private Message selectedMessage;
- private final OnClickListener mEnableAccountListener = new OnClickListener() {
- @Override
- public void onClick(View v) {
- final Account account = conversation == null ? null : conversation.getAccount();
- if (account != null) {
- account.setOption(Account.OPTION_DISABLED, false);
- activity.xmppConnectionService.updateAccount(account);
- }
- }
- };
- private final OnClickListener mUnblockClickListener = new OnClickListener() {
- @Override
- public void onClick(final View v) {
- v.post(() -> v.setVisibility(View.INVISIBLE));
- if (conversation.isDomainBlocked()) {
- BlockContactDialog.show(activity, conversation);
- } else {
- unblockConversation(conversation);
- }
- }
- };
+ private final OnClickListener mEnableAccountListener =
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final Account account = conversation == null ? null : conversation.getAccount();
+ if (account != null) {
+ account.setOption(Account.OPTION_DISABLED, false);
+ activity.xmppConnectionService.updateAccount(account);
+ }
+ }
+ };
+ private final OnClickListener mUnblockClickListener =
+ new OnClickListener() {
+ @Override
+ public void onClick(final View v) {
+ v.post(() -> v.setVisibility(View.INVISIBLE));
+ if (conversation.isDomainBlocked()) {
+ BlockContactDialog.show(activity, conversation);
+ } else {
+ unblockConversation(conversation);
+ }
+ }
+ };
private final OnClickListener mBlockClickListener = this::showBlockSubmenu;
- private final OnClickListener mAddBackClickListener = new OnClickListener() {
-
- @Override
- public void onClick(View v) {
- final Contact contact = conversation == null ? null : conversation.getContact();
- if (contact != null) {
- activity.xmppConnectionService.createContact(contact, true);
- activity.switchToContactDetails(contact);
- }
- }
- };
+ private final OnClickListener mAddBackClickListener =
+ new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ final Contact contact = conversation == null ? null : conversation.getContact();
+ if (contact != null) {
+ activity.xmppConnectionService.createContact(contact, true);
+ activity.switchToContactDetails(contact);
+ }
+ }
+ };
private final View.OnLongClickListener mLongPressBlockListener = this::showBlockSubmenu;
- private final OnClickListener mAllowPresenceSubscription = new OnClickListener() {
- @Override
- public void onClick(View v) {
- final Contact contact = conversation == null ? null : conversation.getContact();
- if (contact != null) {
- activity.xmppConnectionService.sendPresencePacket(contact.getAccount(),
- activity.xmppConnectionService.getPresenceGenerator()
- .sendPresenceUpdatesTo(contact));
- hideSnackbar();
- }
- }
- };
- protected OnClickListener clickToDecryptListener = new OnClickListener() {
-
- @Override
- public void onClick(View v) {
- PendingIntent pendingIntent = conversation.getAccount().getPgpDecryptionService().getPendingIntent();
- if (pendingIntent != null) {
- try {
- getActivity().startIntentSenderForResult(pendingIntent.getIntentSender(),
- REQUEST_DECRYPT_PGP,
- null,
- 0,
- 0,
- 0);
- } catch (SendIntentException e) {
- Toast.makeText(getActivity(), R.string.unable_to_connect_to_keychain, Toast.LENGTH_SHORT).show();
- conversation.getAccount().getPgpDecryptionService().continueDecryption(true);
+ private final OnClickListener mAllowPresenceSubscription =
+ new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ final Contact contact = conversation == null ? null : conversation.getContact();
+ if (contact != null) {
+ activity.xmppConnectionService.sendPresencePacket(
+ contact.getAccount(),
+ activity.xmppConnectionService
+ .getPresenceGenerator()
+ .sendPresenceUpdatesTo(contact));
+ hideSnackbar();
+ }
}
- }
- updateSnackBar(conversation);
- }
- };
+ };
+ protected OnClickListener clickToDecryptListener =
+ new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ PendingIntent pendingIntent =
+ conversation.getAccount().getPgpDecryptionService().getPendingIntent();
+ if (pendingIntent != null) {
+ try {
+ getActivity()
+ .startIntentSenderForResult(
+ pendingIntent.getIntentSender(),
+ REQUEST_DECRYPT_PGP,
+ null,
+ 0,
+ 0,
+ 0);
+ } catch (SendIntentException e) {
+ Toast.makeText(
+ getActivity(),
+ R.string.unable_to_connect_to_keychain,
+ Toast.LENGTH_SHORT)
+ .show();
+ conversation
+ .getAccount()
+ .getPgpDecryptionService()
+ .continueDecryption(true);
+ }
+ }
+ updateSnackBar(conversation);
+ }
+ };
private final AtomicBoolean mSendingPgpMessage = new AtomicBoolean(false);
- private final OnEditorActionListener mEditorActionListener = (v, actionId, event) -> {
- if (actionId == EditorInfo.IME_ACTION_SEND) {
- InputMethodManager imm = (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE);
- if (imm != null && imm.isFullscreenMode()) {
- imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
- }
- sendMessage();
- return true;
- } else {
- return false;
- }
- };
- private final OnClickListener mScrollButtonListener = new OnClickListener() {
-
- @Override
- public void onClick(View v) {
- stopScrolling();
- setSelection(binding.messagesView.getCount() - 1, true);
- }
- };
- private final OnClickListener mSendButtonListener = new OnClickListener() {
-
- @Override
- public void onClick(View v) {
- Object tag = v.getTag();
- if (tag instanceof SendButtonAction) {
- SendButtonAction action = (SendButtonAction) tag;
- switch (action) {
- case TAKE_PHOTO:
- case RECORD_VIDEO:
- case SEND_LOCATION:
- case RECORD_VOICE:
- case CHOOSE_PICTURE:
- attachFile(action.toChoice());
- break;
- case CANCEL:
- if (conversation != null) {
- if (conversation.setCorrectingMessage(null)) {
- binding.textinput.setText("");
- binding.textinput.append(conversation.getDraftMessage());
- conversation.setDraftMessage(null);
- } else if (conversation.getMode() == Conversation.MODE_MULTI) {
- conversation.setNextCounterpart(null);
- binding.textinput.setText("");
- } else {
- binding.textinput.setText("");
- }
- updateChatMsgHint();
- updateSendButton();
- updateEditablity();
+ private final OnEditorActionListener mEditorActionListener =
+ (v, actionId, event) -> {
+ if (actionId == EditorInfo.IME_ACTION_SEND) {
+ InputMethodManager imm =
+ (InputMethodManager)
+ activity.getSystemService(Context.INPUT_METHOD_SERVICE);
+ if (imm != null && imm.isFullscreenMode()) {
+ imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
+ }
+ sendMessage();
+ return true;
+ } else {
+ return false;
+ }
+ };
+ private final OnClickListener mScrollButtonListener =
+ new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ stopScrolling();
+ setSelection(binding.messagesView.getCount() - 1, true);
+ }
+ };
+ private final OnClickListener mSendButtonListener =
+ new OnClickListener() {
+
+ @Override
+ public void onClick(View v) {
+ Object tag = v.getTag();
+ if (tag instanceof SendButtonAction) {
+ SendButtonAction action = (SendButtonAction) tag;
+ switch (action) {
+ case TAKE_PHOTO:
+ case RECORD_VIDEO:
+ case SEND_LOCATION:
+ case RECORD_VOICE:
+ case CHOOSE_PICTURE:
+ attachFile(action.toChoice());
+ break;
+ case CANCEL:
+ if (conversation != null) {
+ if (conversation.setCorrectingMessage(null)) {
+ binding.textinput.setText("");
+ binding.textinput.append(conversation.getDraftMessage());
+ conversation.setDraftMessage(null);
+ } else if (conversation.getMode() == Conversation.MODE_MULTI) {
+ conversation.setNextCounterpart(null);
+ binding.textinput.setText("");
+ } else {
+ binding.textinput.setText("");
+ }
+ updateChatMsgHint();
+ updateSendButton();
+ updateEditablity();
+ }
+ break;
+ default:
+ sendMessage();
}
- break;
- default:
+ } else {
sendMessage();
+ }
}
- } else {
- sendMessage();
- }
- }
- };
+ };
private int completionIndex = 0;
private int lastCompletionLength = 0;
private String incomplete;
@@ -531,7 +632,9 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
return (ConversationFragment) fragment;
} else {
fragment = fragmentManager.findFragmentById(R.id.secondary_fragment);
- return fragment instanceof ConversationFragment ? (ConversationFragment) fragment : null;
+ return fragment instanceof ConversationFragment
+ ? (ConversationFragment) fragment
+ : null;
}
}
@@ -593,7 +696,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
next = next.next();
}
-
}
}
return -1;
@@ -601,7 +703,9 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
private ScrollState getScrollPosition() {
final ListView listView = this.binding == null ? null : this.binding.messagesView;
- if (listView == null || listView.getCount() == 0 || listView.getLastVisiblePosition() == listView.getCount() - 1) {
+ if (listView == null
+ || listView.getCount() == 0
+ || listView.getLastVisiblePosition() == listView.getCount() - 1) {
return null;
} else {
final int pos = listView.getFirstVisiblePosition();
@@ -619,10 +723,12 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
this.lastMessageUuid = lastMessageUuid;
if (lastMessageUuid != null) {
- binding.unreadCountCustomView.setUnreadCount(conversation.getReceivedMessagesCountSinceUuid(lastMessageUuid));
+ binding.unreadCountCustomView.setUnreadCount(
+ conversation.getReceivedMessagesCountSinceUuid(lastMessageUuid));
}
- //TODO maybe this needs a 'post'
- this.binding.messagesView.setSelectionFromTop(scrollPosition.position, scrollPosition.offset);
+ // TODO maybe this needs a 'post'
+ this.binding.messagesView.setSelectionFromTop(
+ scrollPosition.position, scrollPosition.offset);
toggleScrollDownButton();
}
}
@@ -631,61 +737,65 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
if (conversation == null) {
return;
}
- activity.xmppConnectionService.attachLocationToConversation(conversation, uri, new UiCallback() {
-
- @Override
- public void success(Message message) {
-
- }
+ activity.xmppConnectionService.attachLocationToConversation(
+ conversation,
+ uri,
+ new UiCallback() {
- @Override
- public void error(int errorCode, Message object) {
- //TODO show possible pgp error
- }
+ @Override
+ public void success(Message message) {}
- @Override
- public void userInputRequired(PendingIntent pi, Message object) {
+ @Override
+ public void error(int errorCode, Message object) {
+ // TODO show possible pgp error
+ }
- }
- });
+ @Override
+ public void userInputRequired(PendingIntent pi, Message object) {}
+ });
}
private void attachFileToConversation(Conversation conversation, Uri uri, String type) {
if (conversation == null) {
return;
}
- final Toast prepareFileToast = Toast.makeText(getActivity(), getText(R.string.preparing_file), Toast.LENGTH_LONG);
+ final Toast prepareFileToast =
+ Toast.makeText(getActivity(), getText(R.string.preparing_file), Toast.LENGTH_LONG);
prepareFileToast.show();
activity.delegateUriPermissionsToService(uri);
- activity.xmppConnectionService.attachFileToConversation(conversation, uri, type, new UiInformableCallback() {
- @Override
- public void inform(final String text) {
- hidePrepareFileToast(prepareFileToast);
- runOnUiThread(() -> activity.replaceToast(text));
- }
-
- @Override
- public void success(Message message) {
- runOnUiThread(() -> activity.hideToast());
- hidePrepareFileToast(prepareFileToast);
- }
+ activity.xmppConnectionService.attachFileToConversation(
+ conversation,
+ uri,
+ type,
+ new UiInformableCallback() {
+ @Override
+ public void inform(final String text) {
+ hidePrepareFileToast(prepareFileToast);
+ runOnUiThread(() -> activity.replaceToast(text));
+ }
- @Override
- public void error(final int errorCode, Message message) {
- hidePrepareFileToast(prepareFileToast);
- runOnUiThread(() -> activity.replaceToast(getString(errorCode)));
+ @Override
+ public void success(Message message) {
+ runOnUiThread(() -> activity.hideToast());
+ hidePrepareFileToast(prepareFileToast);
+ }
- }
+ @Override
+ public void error(final int errorCode, Message message) {
+ hidePrepareFileToast(prepareFileToast);
+ runOnUiThread(() -> activity.replaceToast(getString(errorCode)));
+ }
- @Override
- public void userInputRequired(PendingIntent pi, Message message) {
- hidePrepareFileToast(prepareFileToast);
- }
- });
+ @Override
+ public void userInputRequired(PendingIntent pi, Message message) {
+ hidePrepareFileToast(prepareFileToast);
+ }
+ });
}
public void attachEditorContentToConversation(Uri uri) {
- mediaPreviewAdapter.addMediaPreviews(Attachment.of(getActivity(), uri, Attachment.Type.FILE));
+ mediaPreviewAdapter.addMediaPreviews(
+ Attachment.of(getActivity(), uri, Attachment.Type.FILE));
toggleInputMethod();
}
@@ -693,10 +803,14 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
if (conversation == null) {
return;
}
- final Toast prepareFileToast = Toast.makeText(getActivity(), getText(R.string.preparing_image), Toast.LENGTH_LONG);
+ final Toast prepareFileToast =
+ Toast.makeText(getActivity(), getText(R.string.preparing_image), Toast.LENGTH_LONG);
prepareFileToast.show();
activity.delegateUriPermissionsToService(uri);
- activity.xmppConnectionService.attachImageToConversation(conversation, uri, type,
+ activity.xmppConnectionService.attachImageToConversation(
+ conversation,
+ uri,
+ type,
new UiCallback() {
@Override
@@ -762,19 +876,31 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
private boolean trustKeysIfNeeded(final Conversation conversation, final int requestCode) {
- return conversation.getNextEncryption() == Message.ENCRYPTION_AXOLOTL && trustKeysIfNeeded(requestCode);
+ return conversation.getNextEncryption() == Message.ENCRYPTION_AXOLOTL
+ && trustKeysIfNeeded(requestCode);
}
protected boolean trustKeysIfNeeded(int requestCode) {
AxolotlService axolotlService = conversation.getAccount().getAxolotlService();
final List targets = axolotlService.getCryptoTargets(conversation);
boolean hasUnaccepted = !conversation.getAcceptedCryptoTargets().containsAll(targets);
- boolean hasUndecidedOwn = !axolotlService.getKeysWithTrust(FingerprintStatus.createActiveUndecided()).isEmpty();
- boolean hasUndecidedContacts = !axolotlService.getKeysWithTrust(FingerprintStatus.createActiveUndecided(), targets).isEmpty();
+ boolean hasUndecidedOwn =
+ !axolotlService
+ .getKeysWithTrust(FingerprintStatus.createActiveUndecided())
+ .isEmpty();
+ boolean hasUndecidedContacts =
+ !axolotlService
+ .getKeysWithTrust(FingerprintStatus.createActiveUndecided(), targets)
+ .isEmpty();
boolean hasPendingKeys = !axolotlService.findDevicesWithoutSession(conversation).isEmpty();
boolean hasNoTrustedKeys = axolotlService.anyTargetHasNoTrustedKeys(targets);
boolean downloadInProgress = axolotlService.hasPendingKeyFetches(targets);
- if (hasUndecidedOwn || hasUndecidedContacts || hasPendingKeys || hasNoTrustedKeys || hasUnaccepted || downloadInProgress) {
+ if (hasUndecidedOwn
+ || hasUndecidedContacts
+ || hasPendingKeys
+ || hasNoTrustedKeys
+ || hasUnaccepted
+ || downloadInProgress) {
axolotlService.createSessionsIfNeeded(conversation);
Intent intent = new Intent(getActivity(), TrustKeysActivity.class);
String[] contacts = new String[targets.size()];
@@ -782,7 +908,9 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
contacts[i] = targets.get(i).toString();
}
intent.putExtra("contacts", contacts);
- intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().asBareJid().toEscapedString());
+ intent.putExtra(
+ EXTRA_ACCOUNT,
+ conversation.getAccount().getJid().asBareJid().toEscapedString());
intent.putExtra("conversation", conversation.getUuid());
startActivityForResult(intent, requestCode);
return true;
@@ -799,9 +927,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
} else if (multi && conversation.getNextCounterpart() != null) {
this.binding.textinput.setHint(R.string.send_message);
this.binding.textInputHint.setVisibility(View.VISIBLE);
- this.binding.textInputHint.setText(getString(
- R.string.send_private_message_to,
- conversation.getNextCounterpart().getResource()));
+ this.binding.textInputHint.setText(
+ getString(
+ R.string.send_private_message_to,
+ conversation.getNextCounterpart().getResource()));
} else if (multi && !conversation.getMucOptions().participating()) {
this.binding.textInputHint.setVisibility(View.GONE);
this.binding.textinput.setHint(R.string.you_are_not_participating);
@@ -839,14 +968,16 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
triggerRtpSession(RtpSessionActivity.ACTION_MAKE_VIDEO_CALL);
break;
case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
- final List imageUris = Attachment.extractAttachments(getActivity(), data, Attachment.Type.IMAGE);
+ final List imageUris =
+ Attachment.extractAttachments(getActivity(), data, Attachment.Type.IMAGE);
mediaPreviewAdapter.addMediaPreviews(imageUris);
toggleInputMethod();
break;
case ATTACHMENT_CHOICE_TAKE_PHOTO:
final Uri takePhotoUri = pendingTakePhotoUri.pop();
if (takePhotoUri != null) {
- mediaPreviewAdapter.addMediaPreviews(Attachment.of(getActivity(), takePhotoUri, Attachment.Type.IMAGE));
+ mediaPreviewAdapter.addMediaPreviews(
+ Attachment.of(getActivity(), takePhotoUri, Attachment.Type.IMAGE));
toggleInputMethod();
} else {
Log.d(Config.LOGTAG, "lost take photo uri. unable to to attach");
@@ -855,8 +986,12 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
case ATTACHMENT_CHOICE_CHOOSE_FILE:
case ATTACHMENT_CHOICE_RECORD_VIDEO:
case ATTACHMENT_CHOICE_RECORD_VOICE:
- final Attachment.Type type = requestCode == ATTACHMENT_CHOICE_RECORD_VOICE ? Attachment.Type.RECORDING : Attachment.Type.FILE;
- final List fileUris = Attachment.extractAttachments(getActivity(), data, type);
+ final Attachment.Type type =
+ requestCode == ATTACHMENT_CHOICE_RECORD_VOICE
+ ? Attachment.Type.RECORDING
+ : Attachment.Type.FILE;
+ final List fileUris =
+ Attachment.extractAttachments(getActivity(), data, type);
mediaPreviewAdapter.addMediaPreviews(fileUris);
toggleInputMethod();
break;
@@ -870,14 +1005,17 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
} else {
geo = Uri.parse(String.format("geo:%s,%s", latitude, longitude));
}
- mediaPreviewAdapter.addMediaPreviews(Attachment.of(getActivity(), geo, Attachment.Type.LOCATION));
+ mediaPreviewAdapter.addMediaPreviews(
+ Attachment.of(getActivity(), geo, Attachment.Type.LOCATION));
toggleInputMethod();
break;
case REQUEST_INVITE_TO_CONVERSATION:
XmppActivity.ConferenceInvite invite = XmppActivity.ConferenceInvite.parse(data);
if (invite != null) {
if (invite.execute(activity)) {
- activity.mToast = Toast.makeText(activity, R.string.creating_conference, Toast.LENGTH_LONG);
+ activity.mToast =
+ Toast.makeText(
+ activity, R.string.creating_conference, Toast.LENGTH_LONG);
activity.mToast.show();
}
}
@@ -887,40 +1025,51 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
private void commitAttachments() {
final List attachments = mediaPreviewAdapter.getAttachments();
- if (anyNeedsExternalStoragePermission(attachments) && !hasPermissions(REQUEST_COMMIT_ATTACHMENTS, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+ if (anyNeedsExternalStoragePermission(attachments)
+ && !hasPermissions(
+ REQUEST_COMMIT_ATTACHMENTS, Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
return;
}
if (trustKeysIfNeeded(conversation, REQUEST_TRUST_KEYS_ATTACHMENTS)) {
return;
}
- final PresenceSelector.OnPresenceSelected callback = () -> {
- for (Iterator i = attachments.iterator(); i.hasNext(); i.remove()) {
- final Attachment attachment = i.next();
- if (attachment.getType() == Attachment.Type.LOCATION) {
- attachLocationToConversation(conversation, attachment.getUri());
- } else if (attachment.getType() == Attachment.Type.IMAGE) {
- Log.d(Config.LOGTAG, "ConversationsActivity.commitAttachments() - attaching image to conversations. CHOOSE_IMAGE");
- attachImageToConversation(conversation, attachment.getUri(), attachment.getMime());
- } else {
- Log.d(Config.LOGTAG, "ConversationsActivity.commitAttachments() - attaching file to conversations. CHOOSE_FILE/RECORD_VOICE/RECORD_VIDEO");
- attachFileToConversation(conversation, attachment.getUri(), attachment.getMime());
- }
- }
- mediaPreviewAdapter.notifyDataSetChanged();
- toggleInputMethod();
- };
+ final PresenceSelector.OnPresenceSelected callback =
+ () -> {
+ for (Iterator i = attachments.iterator(); i.hasNext(); i.remove()) {
+ final Attachment attachment = i.next();
+ if (attachment.getType() == Attachment.Type.LOCATION) {
+ attachLocationToConversation(conversation, attachment.getUri());
+ } else if (attachment.getType() == Attachment.Type.IMAGE) {
+ Log.d(
+ Config.LOGTAG,
+ "ConversationsActivity.commitAttachments() - attaching image to conversations. CHOOSE_IMAGE");
+ attachImageToConversation(
+ conversation, attachment.getUri(), attachment.getMime());
+ } else {
+ Log.d(
+ Config.LOGTAG,
+ "ConversationsActivity.commitAttachments() - attaching file to conversations. CHOOSE_FILE/RECORD_VOICE/RECORD_VIDEO");
+ attachFileToConversation(
+ conversation, attachment.getUri(), attachment.getMime());
+ }
+ }
+ mediaPreviewAdapter.notifyDataSetChanged();
+ toggleInputMethod();
+ };
if (conversation == null
|| conversation.getMode() == Conversation.MODE_MULTI
|| Attachment.canBeSendInband(attachments)
- || (conversation.getAccount().httpUploadAvailable() && FileBackend.allFilesUnderSize(getActivity(), attachments, getMaxHttpUploadSize(conversation)))) {
+ || (conversation.getAccount().httpUploadAvailable()
+ && FileBackend.allFilesUnderSize(
+ getActivity(), attachments, getMaxHttpUploadSize(conversation)))) {
callback.onPresenceSelected();
} else {
activity.selectPresence(conversation, callback);
}
}
-
- private static boolean anyNeedsExternalStoragePermission(final Collection attachments) {
+ private static boolean anyNeedsExternalStoragePermission(
+ final Collection attachments) {
for (final Attachment attachment : attachments) {
if (attachment.getType() != Attachment.Type.LOCATION) {
return true;
@@ -940,7 +1089,9 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
switch (requestCode) {
case ATTACHMENT_CHOICE_TAKE_PHOTO:
if (pendingTakePhotoUri.clear()) {
- Log.d(Config.LOGTAG, "cleared pending photo uri after negative activity result");
+ Log.d(
+ Config.LOGTAG,
+ "cleared pending photo uri after negative activity result");
}
break;
}
@@ -968,14 +1119,15 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
if (activity instanceof ConversationsActivity) {
this.activity = (ConversationsActivity) activity;
} else {
- throw new IllegalStateException("Trying to attach fragment to activity that is not the ConversationsActivity");
+ throw new IllegalStateException(
+ "Trying to attach fragment to activity that is not the ConversationsActivity");
}
}
@Override
public void onDetach() {
super.onDetach();
- this.activity = null; //TODO maybe not a good idea since some callbacks really need it
+ this.activity = null; // TODO maybe not a good idea since some callbacks really need it
}
@Override
@@ -997,30 +1149,42 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
final MenuItem menuVideoCall = menu.findItem(R.id.action_video_call);
final MenuItem menuTogglePinned = menu.findItem(R.id.action_toggle_pinned);
-
if (conversation != null) {
if (conversation.getMode() == Conversation.MODE_MULTI) {
menuContactDetails.setVisible(false);
menuInviteContact.setVisible(conversation.getMucOptions().canInvite());
- menuMucDetails.setTitle(conversation.getMucOptions().isPrivateAndNonAnonymous() ? R.string.action_muc_details : R.string.channel_details);
+ menuMucDetails.setTitle(
+ conversation.getMucOptions().isPrivateAndNonAnonymous()
+ ? R.string.action_muc_details
+ : R.string.channel_details);
menuCall.setVisible(false);
menuOngoingCall.setVisible(false);
} else {
- final XmppConnectionService service = activity == null ? null : activity.xmppConnectionService;
- final Optional ongoingRtpSession = service == null ? Optional.absent() : service.getJingleConnectionManager().getOngoingRtpConnection(conversation.getContact());
+ final XmppConnectionService service =
+ activity == null ? null : activity.xmppConnectionService;
+ final Optional ongoingRtpSession =
+ service == null
+ ? Optional.absent()
+ : service.getJingleConnectionManager()
+ .getOngoingRtpConnection(conversation.getContact());
if (ongoingRtpSession.isPresent()) {
menuOngoingCall.setVisible(true);
menuCall.setVisible(false);
} else {
menuOngoingCall.setVisible(false);
- final RtpCapability.Capability rtpCapability = RtpCapability.check(conversation.getContact());
- final boolean cameraAvailable = activity != null && activity.isCameraFeatureAvailable();
+ final RtpCapability.Capability rtpCapability =
+ RtpCapability.check(conversation.getContact());
+ final boolean cameraAvailable =
+ activity != null && activity.isCameraFeatureAvailable();
menuCall.setVisible(rtpCapability != RtpCapability.Capability.NONE);
- menuVideoCall.setVisible(rtpCapability == RtpCapability.Capability.VIDEO && cameraAvailable);
+ menuVideoCall.setVisible(
+ rtpCapability == RtpCapability.Capability.VIDEO && cameraAvailable);
}
menuContactDetails.setVisible(!this.conversation.withSelf());
menuMucDetails.setVisible(false);
- menuInviteContact.setVisible(service != null && service.findConferenceServer(conversation.getAccount()) != null);
+ menuInviteContact.setVisible(
+ service != null
+ && service.findConferenceServer(conversation.getAccount()) != null);
}
if (conversation.isMuted()) {
menuMute.setVisible(false);
@@ -1039,14 +1203,17 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
@Override
- public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- this.binding = DataBindingUtil.inflate(inflater, R.layout.fragment_conversation, container, false);
- binding.getRoot().setOnClickListener(null); //TODO why the fuck did we do this?
+ public View onCreateView(
+ final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ this.binding =
+ DataBindingUtil.inflate(inflater, R.layout.fragment_conversation, container, false);
+ binding.getRoot().setOnClickListener(null); // TODO why the fuck did we do this?
- binding.textinput.addTextChangedListener(new StylingHelper.MessageEditorStyler(binding.textinput));
+ binding.textinput.addTextChangedListener(
+ new StylingHelper.MessageEditorStyler(binding.textinput));
binding.textinput.setOnEditorActionListener(mEditorActionListener);
- binding.textinput.setRichContentListener(new String[]{"image/*"}, mEditorContentListener);
+ binding.textinput.setRichContentListener(new String[] {"image/*"}, mEditorContentListener);
binding.textSendButton.setOnClickListener(this.mSendButtonListener);
@@ -1063,7 +1230,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
registerForContextMenu(binding.messagesView);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- this.binding.textinput.setCustomInsertionActionModeCallback(new EditMessageActionModeCallback(this.binding.textinput));
+ this.binding.textinput.setCustomInsertionActionModeCallback(
+ new EditMessageActionModeCallback(this.binding.textinput));
}
return binding.getRoot();
@@ -1081,9 +1249,12 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
if (binding.textinput.isEnabled()) {
binding.textinput.insertAsQuote(text);
binding.textinput.requestFocus();
- InputMethodManager inputMethodManager = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
+ InputMethodManager inputMethodManager =
+ (InputMethodManager)
+ getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
if (inputMethodManager != null) {
- inputMethodManager.showSoftInput(binding.textinput, InputMethodManager.SHOW_IMPLICIT);
+ inputMethodManager.showSoftInput(
+ binding.textinput, InputMethodManager.SHOW_IMPLICIT);
}
}
}
@@ -1094,7 +1265,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
- //This should cancel any remaining click events that would otherwise trigger links
+ // This should cancel any remaining click events that would otherwise trigger links
v.dispatchTouchEvent(MotionEvent.obtain(0, 0, MotionEvent.ACTION_CANCEL, 0f, 0f, 0));
synchronized (this.messageList) {
super.onCreateContextMenu(menu, v, menuInfo);
@@ -1113,18 +1284,26 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
if (m.getType() != Message.TYPE_STATUS && m.getType() != Message.TYPE_RTP_SESSION) {
- if (m.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE || m.getEncryption() == Message.ENCRYPTION_AXOLOTL_FAILED) {
+ if (m.getEncryption() == Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE
+ || m.getEncryption() == Message.ENCRYPTION_AXOLOTL_FAILED) {
return;
}
- if (m.getStatus() == Message.STATUS_RECEIVED && t != null && (t.getStatus() == Transferable.STATUS_CANCELLED || t.getStatus() == Transferable.STATUS_FAILED)) {
+ if (m.getStatus() == Message.STATUS_RECEIVED
+ && t != null
+ && (t.getStatus() == Transferable.STATUS_CANCELLED
+ || t.getStatus() == Transferable.STATUS_FAILED)) {
return;
}
final boolean deleted = m.isDeleted();
- final boolean encrypted = m.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED
- || m.getEncryption() == Message.ENCRYPTION_PGP;
- final boolean receiving = m.getStatus() == Message.STATUS_RECEIVED && (t instanceof JingleFileTransferConnection || t instanceof HttpDownloadConnection);
+ final boolean encrypted =
+ m.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED
+ || m.getEncryption() == Message.ENCRYPTION_PGP;
+ final boolean receiving =
+ m.getStatus() == Message.STATUS_RECEIVED
+ && (t instanceof JingleFileTransferConnection
+ || t instanceof HttpDownloadConnection);
activity.getMenuInflater().inflate(R.menu.message_context, menu);
menu.setHeaderTitle(R.string.message_options);
MenuItem openWith = menu.findItem(R.id.open_with);
@@ -1141,8 +1320,16 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
MenuItem deleteFile = menu.findItem(R.id.delete_file);
MenuItem showErrorMessage = menu.findItem(R.id.show_error_message);
final boolean unInitiatedButKnownSize = MessageUtils.unInitiatedButKnownSize(m);
- final boolean showError = m.getStatus() == Message.STATUS_SEND_FAILED && m.getErrorMessage() != null && !Message.ERROR_MESSAGE_CANCELLED.equals(m.getErrorMessage());
- if (!m.isFileOrImage() && !encrypted && !m.isGeoUri() && !m.treatAsDownloadable() && !unInitiatedButKnownSize && t == null) {
+ final boolean showError =
+ m.getStatus() == Message.STATUS_SEND_FAILED
+ && m.getErrorMessage() != null
+ && !Message.ERROR_MESSAGE_CANCELLED.equals(m.getErrorMessage());
+ if (!m.isFileOrImage()
+ && !encrypted
+ && !m.isGeoUri()
+ && !m.treatAsDownloadable()
+ && !unInitiatedButKnownSize
+ && t == null) {
copyMessage.setVisible(true);
quoteMessage.setVisible(!showError && MessageUtils.prepareQuote(m).length() > 0);
String body = m.getMergedBody().toString();
@@ -1163,7 +1350,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
&& m.getConversation() instanceof Conversation) {
correctMessage.setVisible(true);
}
- if ((m.isFileOrImage() && !deleted && !receiving) || (m.getType() == Message.TYPE_TEXT && !m.treatAsDownloadable()) && !unInitiatedButKnownSize && t == null) {
+ if ((m.isFileOrImage() && !deleted && !receiving)
+ || (m.getType() == Message.TYPE_TEXT && !m.treatAsDownloadable())
+ && !unInitiatedButKnownSize
+ && t == null) {
shareWith.setVisible(true);
}
if (m.getStatus() == Message.STATUS_SEND_FAILED) {
@@ -1178,27 +1368,38 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
if (m.isFileOrImage() && deleted && m.hasFileOnRemoteHost()) {
downloadFile.setVisible(true);
- downloadFile.setTitle(activity.getString(R.string.download_x_file, UIHelper.getFileDescriptionString(activity, m)));
- }
- final boolean waitingOfferedSending = m.getStatus() == Message.STATUS_WAITING
- || m.getStatus() == Message.STATUS_UNSEND
- || m.getStatus() == Message.STATUS_OFFERED;
- final boolean cancelable = (t != null && !deleted) || waitingOfferedSending && m.needsUploading();
+ downloadFile.setTitle(
+ activity.getString(
+ R.string.download_x_file,
+ UIHelper.getFileDescriptionString(activity, m)));
+ }
+ final boolean waitingOfferedSending =
+ m.getStatus() == Message.STATUS_WAITING
+ || m.getStatus() == Message.STATUS_UNSEND
+ || m.getStatus() == Message.STATUS_OFFERED;
+ final boolean cancelable =
+ (t != null && !deleted) || waitingOfferedSending && m.needsUploading();
if (cancelable) {
cancelTransmission.setVisible(true);
}
if (m.isFileOrImage() && !deleted && !cancelable) {
final String path = m.getRelativeFilePath();
- if (path == null || !path.startsWith("/") || FileBackend.inConversationsDirectory(requireActivity(), path)) {
+ if (path == null
+ || !path.startsWith("/")
+ || FileBackend.inConversationsDirectory(requireActivity(), path)) {
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)));
}
}
if (showError) {
showErrorMessage.setVisible(true);
}
final String mime = m.isFileOrImage() ? m.getMimeType() : null;
- if ((m.isGeoUri() && GeoHelper.openInOsmAnd(getActivity(), m)) || (mime != null && mime.startsWith("audio/"))) {
+ if ((m.isGeoUri() && GeoHelper.openInOsmAnd(getActivity(), m))
+ || (mime != null && mime.startsWith("audio/"))) {
openWith.setVisible(true);
}
}
@@ -1285,7 +1486,9 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
ConferenceDetailsActivity.open(getActivity(), conversation);
break;
case R.id.action_invite:
- startActivityForResult(ChooseContactActivity.create(activity, conversation), REQUEST_INVITE_TO_CONVERSATION);
+ startActivityForResult(
+ ChooseContactActivity.create(activity, conversation),
+ REQUEST_INVITE_TO_CONVERSATION);
break;
case R.id.action_clear_history:
clearHistoryDialog(conversation);
@@ -1294,7 +1497,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
muteConversationDialog(conversation);
break;
case R.id.action_unmute:
- unmuteConversation(conversation);
+ unMuteConversation(conversation);
break;
case R.id.action_block:
case R.id.action_unblock:
@@ -1328,11 +1531,17 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
private void returnToOngoingCall() {
- final Optional ongoingRtpSession = activity.xmppConnectionService.getJingleConnectionManager().getOngoingRtpConnection(conversation.getContact());
+ final Optional ongoingRtpSession =
+ activity.xmppConnectionService
+ .getJingleConnectionManager()
+ .getOngoingRtpConnection(conversation.getContact());
if (ongoingRtpSession.isPresent()) {
final OngoingRtpSession id = ongoingRtpSession.get();
final Intent intent = new Intent(activity, RtpSessionActivity.class);
intent.putExtra(RtpSessionActivity.EXTRA_ACCOUNT, id.getAccount().getJid().asBareJid().toEscapedString());
+ intent.putExtra(
+ RtpSessionActivity.EXTRA_ACCOUNT,
+ id.getAccount().getJid().asBareJid().toEscapedString());
intent.putExtra(RtpSessionActivity.EXTRA_WITH, id.getWith().toEscapedString());
if (id instanceof AbstractJingleConnection.Id) {
intent.setAction(Intent.ACTION_VIEW);
@@ -1346,11 +1555,11 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
activity.startActivity(intent);
}
-
}
private void togglePinned() {
- final boolean pinned = conversation.getBooleanAttribute(Conversation.ATTRIBUTE_PINNED_ON_TOP, false);
+ final boolean pinned =
+ conversation.getBooleanAttribute(Conversation.ATTRIBUTE_PINNED_ON_TOP, false);
conversation.setAttribute(Conversation.ATTRIBUTE_PINNED_ON_TOP, !pinned);
activity.xmppConnectionService.updateConversation(conversation);
activity.invalidateOptionsMenu();
@@ -1361,7 +1570,16 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
Toast.makeText(activity, R.string.disable_tor_to_make_call, Toast.LENGTH_SHORT).show();
return;
}
- if (hasPermissions(REQUEST_START_AUDIO_CALL, Manifest.permission.RECORD_AUDIO)) {
+ final List permissions;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ permissions =
+ Arrays.asList(
+ Manifest.permission.RECORD_AUDIO,
+ Manifest.permission.BLUETOOTH_CONNECT);
+ } else {
+ permissions = Collections.singletonList(Manifest.permission.RECORD_AUDIO);
+ }
+ if (hasPermissions(REQUEST_START_AUDIO_CALL, permissions)) {
triggerRtpSession(RtpSessionActivity.ACTION_MAKE_VOICE_CALL);
}
}
@@ -1371,15 +1589,26 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
Toast.makeText(activity, R.string.disable_tor_to_make_call, Toast.LENGTH_SHORT).show();
return;
}
- if (hasPermissions(REQUEST_START_VIDEO_CALL, Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)) {
+ final List permissions;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ permissions =
+ Arrays.asList(
+ Manifest.permission.RECORD_AUDIO,
+ Manifest.permission.CAMERA,
+ Manifest.permission.BLUETOOTH_CONNECT);
+ } else {
+ permissions =
+ Arrays.asList(Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA);
+ }
+ if (hasPermissions(REQUEST_START_VIDEO_CALL, permissions)) {
triggerRtpSession(RtpSessionActivity.ACTION_MAKE_VIDEO_CALL);
}
}
-
private void triggerRtpSession(final String action) {
if (activity.xmppConnectionService.getJingleConnectionManager().isBusy() != null) {
- Toast.makeText(getActivity(), R.string.only_one_call_at_a_time, Toast.LENGTH_LONG).show();
+ Toast.makeText(getActivity(), R.string.only_one_call_at_a_time, Toast.LENGTH_LONG)
+ .show();
return;
}
final Contact contact = conversation.getContact();
@@ -1392,9 +1621,13 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
} else {
capability = RtpCapability.Capability.AUDIO;
}
- PresenceSelector.selectFullJidForDirectRtpConnection(activity, contact, capability, fullJid -> {
- triggerRtpSession(contact.getAccount(), fullJid, action);
- });
+ PresenceSelector.selectFullJidForDirectRtpConnection(
+ activity,
+ contact,
+ capability,
+ fullJid -> {
+ triggerRtpSession(contact.getAccount(), fullJid, action);
+ });
}
}
@@ -1448,7 +1681,11 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
item.setChecked(true);
} else {
updated = false;
- activity.announcePgp(conversation.getAccount(), conversation, null, activity.onOpenPGPKeyPublished);
+ activity.announcePgp(
+ conversation.getAccount(),
+ conversation,
+ null,
+ activity.onOpenPGPKeyPublished);
}
} else {
activity.showInstallPgpDialog();
@@ -1456,8 +1693,11 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
break;
case R.id.encryption_choice_axolotl:
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(conversation.getAccount())
- + "Enabled axolotl for Contact " + conversation.getContact().getJid());
+ Log.d(
+ Config.LOGTAG,
+ AxolotlService.getLogprefix(conversation.getAccount())
+ + "Enabled axolotl for Contact "
+ + conversation.getContact().getJid());
updated = conversation.setNextEncryption(Message.ENCRYPTION_AXOLOTL);
item.setChecked(true);
break;
@@ -1479,11 +1719,18 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
public void attachFile(final int attachmentChoice, final boolean updateRecentlyUsed) {
if (attachmentChoice == ATTACHMENT_CHOICE_RECORD_VOICE) {
- if (!hasPermissions(attachmentChoice, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.RECORD_AUDIO)) {
+ if (!hasPermissions(
+ attachmentChoice,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.RECORD_AUDIO)) {
return;
}
- } else if (attachmentChoice == ATTACHMENT_CHOICE_TAKE_PHOTO || attachmentChoice == ATTACHMENT_CHOICE_RECORD_VIDEO) {
- if (!hasPermissions(attachmentChoice, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA)) {
+ } else if (attachmentChoice == ATTACHMENT_CHOICE_TAKE_PHOTO
+ || attachmentChoice == ATTACHMENT_CHOICE_RECORD_VIDEO) {
+ if (!hasPermissions(
+ attachmentChoice,
+ Manifest.permission.WRITE_EXTERNAL_STORAGE,
+ Manifest.permission.CAMERA)) {
return;
}
} else if (attachmentChoice != ATTACHMENT_CHOICE_LOCATION) {
@@ -1498,39 +1745,50 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
final int mode = conversation.getMode();
if (encryption == Message.ENCRYPTION_PGP) {
if (activity.hasPgp()) {
- if (mode == Conversation.MODE_SINGLE && conversation.getContact().getPgpKeyId() != 0) {
- activity.xmppConnectionService.getPgpEngine().hasKey(
- conversation.getContact(),
- new UiCallback() {
-
- @Override
- public void userInputRequired(PendingIntent pi, Contact contact) {
- startPendingIntent(pi, attachmentChoice);
- }
+ if (mode == Conversation.MODE_SINGLE
+ && conversation.getContact().getPgpKeyId() != 0) {
+ activity.xmppConnectionService
+ .getPgpEngine()
+ .hasKey(
+ conversation.getContact(),
+ new UiCallback() {
+
+ @Override
+ public void userInputRequired(
+ PendingIntent pi, Contact contact) {
+ startPendingIntent(pi, attachmentChoice);
+ }
- @Override
- public void success(Contact contact) {
- invokeAttachFileIntent(attachmentChoice);
- }
+ @Override
+ public void success(Contact contact) {
+ invokeAttachFileIntent(attachmentChoice);
+ }
- @Override
- public void error(int error, Contact contact) {
- activity.replaceToast(getString(error));
- }
- });
- } else if (mode == Conversation.MODE_MULTI && conversation.getMucOptions().pgpKeysInUse()) {
+ @Override
+ public void error(int error, Contact contact) {
+ activity.replaceToast(getString(error));
+ }
+ });
+ } else if (mode == Conversation.MODE_MULTI
+ && conversation.getMucOptions().pgpKeysInUse()) {
if (!conversation.getMucOptions().everybodyHasKeys()) {
- Toast warning = Toast.makeText(getActivity(), R.string.missing_public_keys, Toast.LENGTH_LONG);
+ Toast warning =
+ Toast.makeText(
+ getActivity(),
+ R.string.missing_public_keys,
+ Toast.LENGTH_LONG);
warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
warning.show();
}
invokeAttachFileIntent(attachmentChoice);
} else {
- showNoPGPKeyDialog(false, (dialog, which) -> {
- conversation.setNextEncryption(Message.ENCRYPTION_NONE);
- activity.xmppConnectionService.updateConversation(conversation);
- invokeAttachFileIntent(attachmentChoice);
- });
+ showNoPGPKeyDialog(
+ false,
+ (dialog, which) -> {
+ conversation.setNextEncryption(Message.ENCRYPTION_NONE);
+ activity.xmppConnectionService.updateConversation(conversation);
+ invokeAttachFileIntent(attachmentChoice);
+ });
}
} else {
activity.showInstallPgpDialog();
@@ -1542,18 +1800,24 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
private void storeRecentlyUsedQuickAction(final int attachmentChoice) {
try {
- activity.getPreferences().edit()
- .putString(RECENTLY_USED_QUICK_ACTION, SendButtonAction.of(attachmentChoice).toString())
+ activity.getPreferences()
+ .edit()
+ .putString(
+ RECENTLY_USED_QUICK_ACTION,
+ SendButtonAction.of(attachmentChoice).toString())
.apply();
} catch (IllegalArgumentException e) {
- //just do not save
+ // just do not save
}
}
@Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ public void onRequestPermissionsResult(
+ int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
+ final PermissionUtils.PermissionResult permissionResult =
+ PermissionUtils.removeBluetoothConnect(permissions, grantResults);
if (grantResults.length > 0) {
- if (allGranted(grantResults)) {
+ if (allGranted(permissionResult.grantResults)) {
switch (requestCode) {
case REQUEST_START_DOWNLOAD:
if (this.mPendingDownloadableMessage != null) {
@@ -1580,7 +1844,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
} else {
@StringRes int res;
- String firstDenied = getFirstDenied(grantResults, permissions);
+ String firstDenied =
+ getFirstDenied(permissionResult.grantResults, permissionResult.permissions);
if (Manifest.permission.RECORD_AUDIO.equals(firstDenied)) {
res = R.string.no_microphone_permission;
} else if (Manifest.permission.CAMERA.equals(firstDenied)) {
@@ -1588,7 +1853,11 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
} else {
res = R.string.no_storage_permission;
}
- Toast.makeText(getActivity(), getString(res, getString(R.string.app_name)), Toast.LENGTH_SHORT).show();
+ Toast.makeText(
+ getActivity(),
+ getString(res, getString(R.string.app_name)),
+ Toast.LENGTH_SHORT)
+ .show();
}
}
if (writeGranted(grantResults, permissions)) {
@@ -1613,41 +1882,53 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
if (!transferable.start()) {
Log.d(Config.LOGTAG, "type: " + transferable.getClass().getName());
- Toast.makeText(getActivity(), R.string.not_connected_try_again, Toast.LENGTH_SHORT).show();
+ Toast.makeText(getActivity(), R.string.not_connected_try_again, Toast.LENGTH_SHORT)
+ .show();
}
- } else if (message.treatAsDownloadable() || message.hasFileOnRemoteHost() || MessageUtils.unInitiatedButKnownSize(message)) {
+ } else if (message.treatAsDownloadable()
+ || message.hasFileOnRemoteHost()
+ || MessageUtils.unInitiatedButKnownSize(message)) {
createNewConnection(message);
} else {
- Log.d(Config.LOGTAG, message.getConversation().getAccount() + ": unable to start downloadable");
+ Log.d(
+ Config.LOGTAG,
+ message.getConversation().getAccount() + ": unable to start downloadable");
}
}
private void createNewConnection(final Message message) {
if (!activity.xmppConnectionService.hasInternetConnection()) {
- Toast.makeText(getActivity(), R.string.not_connected_try_again, Toast.LENGTH_SHORT).show();
+ Toast.makeText(getActivity(), R.string.not_connected_try_again, Toast.LENGTH_SHORT)
+ .show();
return;
}
- activity.xmppConnectionService.getHttpConnectionManager().createNewDownloadConnection(message, true);
+ activity.xmppConnectionService
+ .getHttpConnectionManager()
+ .createNewDownloadConnection(message, true);
}
@SuppressLint("InflateParams")
protected void clearHistoryDialog(final Conversation conversation) {
final AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity());
builder.setTitle(getString(R.string.clear_conversation_history));
- final View dialogView = requireActivity().getLayoutInflater().inflate(R.layout.dialog_clear_history, null);
- final CheckBox endConversationCheckBox = dialogView.findViewById(R.id.end_conversation_checkbox);
+ final View dialogView =
+ requireActivity().getLayoutInflater().inflate(R.layout.dialog_clear_history, null);
+ final CheckBox endConversationCheckBox =
+ dialogView.findViewById(R.id.end_conversation_checkbox);
builder.setView(dialogView);
builder.setNegativeButton(getString(R.string.cancel), null);
- builder.setPositiveButton(getString(R.string.confirm), (dialog, which) -> {
- this.activity.xmppConnectionService.clearConversationHistory(conversation);
- if (endConversationCheckBox.isChecked()) {
- this.activity.xmppConnectionService.archiveConversation(conversation);
- this.activity.onConversationArchived(conversation);
- } else {
- activity.onConversationsListItemUpdated();
- refresh();
- }
- });
+ builder.setPositiveButton(
+ getString(R.string.confirm),
+ (dialog, which) -> {
+ this.activity.xmppConnectionService.clearConversationHistory(conversation);
+ if (endConversationCheckBox.isChecked()) {
+ this.activity.xmppConnectionService.archiveConversation(conversation);
+ this.activity.onConversationArchived(conversation);
+ } else {
+ activity.onConversationsListItemUpdated();
+ refresh();
+ }
+ });
builder.create().show();
}
@@ -1663,27 +1944,30 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
labels[i] = TimeFrameUtils.resolve(activity, 1000L * durations[i]);
}
}
- builder.setItems(labels, (dialog, which) -> {
- final long till;
- if (durations[which] == -1) {
- till = Long.MAX_VALUE;
- } else {
- till = System.currentTimeMillis() + (durations[which] * 1000L);
- }
- conversation.setMutedTill(till);
- activity.xmppConnectionService.updateConversation(conversation);
- activity.onConversationsListItemUpdated();
- refresh();
- activity.invalidateOptionsMenu();
- });
+ builder.setItems(
+ labels,
+ (dialog, which) -> {
+ final long till;
+ if (durations[which] == -1) {
+ till = Long.MAX_VALUE;
+ } else {
+ till = System.currentTimeMillis() + (durations[which] * 1000L);
+ }
+ conversation.setMutedTill(till);
+ activity.xmppConnectionService.updateConversation(conversation);
+ activity.onConversationsListItemUpdated();
+ refresh();
+ activity.invalidateOptionsMenu();
+ });
builder.create().show();
}
- private boolean hasPermissions(int requestCode, String... permissions) {
+ private boolean hasPermissions(int requestCode, List permissions) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
final List missingPermissions = new ArrayList<>();
for (String permission : permissions) {
- if (Config.ONLY_INTERNAL_STORAGE && permission.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+ if (Config.ONLY_INTERNAL_STORAGE
+ && permission.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
continue;
}
if (activity.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
@@ -1693,7 +1977,9 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
if (missingPermissions.size() == 0) {
return true;
} else {
- requestPermissions(missingPermissions.toArray(new String[missingPermissions.size()]), requestCode);
+ requestPermissions(
+ missingPermissions.toArray(new String[0]),
+ requestCode);
return false;
}
} else {
@@ -1701,7 +1987,11 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
}
- public void unmuteConversation(final Conversation conversation) {
+ private boolean hasPermissions(int requestCode, String... permissions) {
+ return hasPermissions(requestCode, ImmutableList.copyOf(permissions));
+ }
+
+ public void unMuteConversation(final Conversation conversation) {
conversation.setMutedTill(0);
this.activity.xmppConnectionService.updateConversation(conversation);
this.activity.onConversationsListItemUpdated();
@@ -1709,7 +1999,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
this.activity.invalidateOptionsMenu();
}
-
protected void invokeAttachFileIntent(final int attachmentChoice) {
Intent intent = new Intent();
boolean chooser = false;
@@ -1789,7 +2078,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
try {
message = (Message) binding.messagesView.getItemAtPosition(i);
} catch (IndexOutOfBoundsException e) {
- //should not happen if we synchronize properly. however if that fails we just gonna try item -1
+ // should not happen if we synchronize properly. however if that fails we
+ // just gonna try item -1
continue;
}
if (message.getType() != Message.TYPE_STATUS) {
@@ -1811,7 +2101,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
if (message.isGeoUri()) {
GeoHelper.view(getActivity(), message);
} else {
- final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
+ final DownloadableFile file =
+ activity.xmppConnectionService.getFileBackend().getFile(message);
ViewUtil.view(activity, file);
}
}
@@ -1820,7 +2111,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity());
builder.setTitle(R.string.error_message);
final String errorMessage = message.getErrorMessage();
- final String[] errorMessageParts = errorMessage == null ? new String[0] : errorMessage.split("\\u001f");
+ final String[] errorMessageParts =
+ errorMessage == null ? new String[0] : errorMessage.split("\\u001f");
final String displayError;
if (errorMessageParts.length == 2) {
displayError = errorMessageParts[1];
@@ -1828,31 +2120,37 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
displayError = errorMessage;
}
builder.setMessage(displayError);
- builder.setNegativeButton(R.string.copy_to_clipboard, (dialog, which) -> {
- activity.copyTextToClipboard(displayError, R.string.error_message);
- Toast.makeText(activity, R.string.error_message_copied_to_clipboard, Toast.LENGTH_SHORT).show();
- });
+ builder.setNegativeButton(
+ R.string.copy_to_clipboard,
+ (dialog, which) -> {
+ activity.copyTextToClipboard(displayError, R.string.error_message);
+ Toast.makeText(
+ activity,
+ R.string.error_message_copied_to_clipboard,
+ Toast.LENGTH_SHORT)
+ .show();
+ });
builder.setPositiveButton(R.string.confirm, null);
builder.create().show();
}
-
private void deleteFile(final Message message) {
AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity());
builder.setNegativeButton(R.string.cancel, null);
builder.setTitle(R.string.delete_file_dialog);
builder.setMessage(R.string.delete_file_dialog_msg);
- builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
- if (activity.xmppConnectionService.getFileBackend().deleteFile(message)) {
- message.setDeleted(true);
- activity.xmppConnectionService.evictPreview(message.getUuid());
- activity.xmppConnectionService.updateMessage(message, false);
- activity.onConversationsListItemUpdated();
- refresh();
- }
- });
+ builder.setPositiveButton(
+ R.string.confirm,
+ (dialog, which) -> {
+ if (activity.xmppConnectionService.getFileBackend().deleteFile(message)) {
+ message.setDeleted(true);
+ activity.xmppConnectionService.evictPreview(message.getUuid());
+ activity.xmppConnectionService.updateMessage(message, false);
+ activity.onConversationsListItemUpdated();
+ refresh();
+ }
+ });
builder.create().show();
-
}
private void resendMessage(final Message message) {
@@ -1861,21 +2159,29 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
return;
}
final Conversation conversation = (Conversation) message.getConversation();
- final DownloadableFile file = activity.xmppConnectionService.getFileBackend().getFile(message);
+ final DownloadableFile file =
+ activity.xmppConnectionService.getFileBackend().getFile(message);
if ((file.exists() && file.canRead()) || message.hasFileOnRemoteHost()) {
final XmppConnection xmppConnection = conversation.getAccount().getXmppConnection();
if (!message.hasFileOnRemoteHost()
&& xmppConnection != null
&& conversation.getMode() == Conversational.MODE_SINGLE
- && !xmppConnection.getFeatures().httpUpload(message.getFileParams().getSize())) {
- activity.selectPresence(conversation, () -> {
- message.setCounterpart(conversation.getNextCounterpart());
- activity.xmppConnectionService.resendFailedMessages(message);
- new Handler().post(() -> {
- int size = messageList.size();
- this.binding.messagesView.setSelection(size - 1);
- });
- });
+ && !xmppConnection
+ .getFeatures()
+ .httpUpload(message.getFileParams().getSize())) {
+ activity.selectPresence(
+ conversation,
+ () -> {
+ message.setCounterpart(conversation.getNextCounterpart());
+ activity.xmppConnectionService.resendFailedMessages(message);
+ new Handler()
+ .post(
+ () -> {
+ int size = messageList.size();
+ this.binding.messagesView.setSelection(
+ size - 1);
+ });
+ });
return;
}
} else if (!Compatibility.hasStoragePermission(getActivity())) {
@@ -1891,10 +2197,12 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
}
activity.xmppConnectionService.resendFailedMessages(message);
- new Handler().post(() -> {
- int size = messageList.size();
- this.binding.messagesView.setSelection(size - 1);
- });
+ new Handler()
+ .post(
+ () -> {
+ int size = messageList.size();
+ this.binding.messagesView.setSelection(size - 1);
+ });
}
private void cancelTransmission(Message message) {
@@ -1902,7 +2210,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
if (transferable != null) {
transferable.cancel();
} else if (message.getStatus() != Message.STATUS_RECEIVED) {
- activity.xmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED, Message.ERROR_MESSAGE_CANCELLED);
+ activity.xmppConnectionService.markMessage(
+ message, Message.STATUS_SEND_FAILED, Message.ERROR_MESSAGE_CANCELLED);
}
}
@@ -1933,7 +2242,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
this.conversation.setDraftMessage(editable.toString());
this.binding.textinput.setText("");
this.binding.textinput.append(message.getBody());
-
}
private void highlightInConference(String nick) {
@@ -1949,14 +2257,22 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
editable.insert(pos, nick + ": ");
} else {
if (pos > 2 && editable.subSequence(pos - 2, pos).toString().equals(": ")) {
- if (NickValidityChecker.check(conversation, Arrays.asList(editable.subSequence(0, pos - 2).toString().split(", ")))) {
+ if (NickValidityChecker.check(
+ conversation,
+ Arrays.asList(
+ editable.subSequence(0, pos - 2).toString().split(", ")))) {
editable.insert(pos - 2, ", " + nick);
return;
}
}
- editable.insert(pos, (Character.isWhitespace(before) ? "" : " ") + nick + (Character.isWhitespace(after) ? "" : " "));
+ editable.insert(
+ pos,
+ (Character.isWhitespace(before) ? "" : " ")
+ + nick
+ + (Character.isWhitespace(after) ? "" : " "));
if (Character.isWhitespace(after)) {
- this.binding.textinput.setSelection(this.binding.textinput.getSelectionStart() + 1);
+ this.binding.textinput.setSelection(
+ this.binding.textinput.getSelectionStart() + 1);
}
}
}
@@ -1985,7 +2301,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
if (scrollState != null) {
outState.putParcelable(STATE_SCROLL_POSITION, scrollState);
}
- final ArrayList attachments = mediaPreviewAdapter == null ? new ArrayList<>() : mediaPreviewAdapter.getAttachments();
+ final ArrayList attachments =
+ mediaPreviewAdapter == null
+ ? new ArrayList<>()
+ : mediaPreviewAdapter.getAttachments();
if (attachments.size() > 0) {
outState.putParcelableArrayList(STATE_MEDIA_PREVIEWS, attachments);
}
@@ -1999,7 +2318,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
return;
}
String uuid = savedInstanceState.getString(STATE_CONVERSATION_UUID);
- ArrayList attachments = savedInstanceState.getParcelableArrayList(STATE_MEDIA_PREVIEWS);
+ ArrayList attachments =
+ savedInstanceState.getParcelableArrayList(STATE_MEDIA_PREVIEWS);
pendingLastMessageUuid.push(savedInstanceState.getString(STATE_LAST_MESSAGE_UUID, null));
if (uuid != null) {
QuickLoader.set(uuid);
@@ -2024,9 +2344,14 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
if (extras != null) {
processExtras(extras);
}
- } else if (conversation == null && activity != null && activity.xmppConnectionService != null) {
+ } else if (conversation == null
+ && activity != null
+ && activity.xmppConnectionService != null) {
final String uuid = pendingConversationsUuid.pop();
- Log.d(Config.LOGTAG, "ConversationFragment.onStart() - activity was bound but no conversation loaded. uuid=" + uuid);
+ Log.d(
+ Config.LOGTAG,
+ "ConversationFragment.onStart() - activity was bound but no conversation loaded. uuid="
+ + uuid);
if (uuid != null) {
findAndReInitByUuidOrArchive(uuid);
}
@@ -2101,7 +2426,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
return false;
}
this.conversation = conversation;
- //once we set the conversation all is good and it will automatically do the right thing in onStart()
+ // once we set the conversation all is good and it will automatically do the right thing in
+ // onStart()
if (this.activity == null || this.binding == null) {
return false;
}
@@ -2121,12 +2447,16 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
setupIme();
- final boolean scrolledToBottomAndNoPending = this.scrolledToBottom() && pendingScrollState.peek() == null;
+ final boolean scrolledToBottomAndNoPending =
+ this.scrolledToBottom() && pendingScrollState.peek() == null;
- this.binding.textSendButton.setContentDescription(activity.getString(R.string.send_message_to_x, conversation.getName()));
+ this.binding.textSendButton.setContentDescription(
+ activity.getString(R.string.send_message_to_x, conversation.getName()));
this.binding.textinput.setKeyboardListener(null);
this.binding.textinput.setText("");
- final boolean participating = conversation.getMode() == Conversational.MODE_SINGLE || conversation.getMucOptions().participating();
+ final boolean participating =
+ conversation.getMode() == Conversational.MODE_SINGLE
+ || conversation.getMucOptions().participating();
if (participating) {
this.binding.textinput.append(this.conversation.getNextMessage());
}
@@ -2157,10 +2487,12 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
}
-
this.binding.messagesView.post(this::fireReadEvent);
- //TODO if we only do this when this fragment is running on main it won't *bing* in tablet layout which might be unnecessary since we can *see* it
- activity.xmppConnectionService.getNotificationService().setOpenConversation(this.conversation);
+ // TODO if we only do this when this fragment is running on main it won't *bing* in tablet
+ // layout which might be unnecessary since we can *see* it
+ activity.xmppConnectionService
+ .getNotificationService()
+ .setOpenConversation(this.conversation);
return true;
}
@@ -2180,11 +2512,11 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
private void setSelection(int pos, boolean jumpToBottom) {
ListViewUtils.setSelection(this.binding.messagesView, pos, jumpToBottom);
- this.binding.messagesView.post(() -> ListViewUtils.setSelection(this.binding.messagesView, pos, jumpToBottom));
+ this.binding.messagesView.post(
+ () -> ListViewUtils.setSelection(this.binding.messagesView, pos, jumpToBottom));
this.binding.messagesView.post(this::fireReadEvent);
}
-
private boolean scrolledToBottom() {
return this.binding != null && scrolledToBottom(this.binding.messagesView);
}
@@ -2193,18 +2525,22 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
final String downloadUuid = extras.getString(ConversationsActivity.EXTRA_DOWNLOAD_UUID);
final String text = extras.getString(Intent.EXTRA_TEXT);
final String nick = extras.getString(ConversationsActivity.EXTRA_NICK);
- final String postInitAction = extras.getString(ConversationsActivity.EXTRA_POST_INIT_ACTION);
+ final String postInitAction =
+ extras.getString(ConversationsActivity.EXTRA_POST_INIT_ACTION);
final boolean asQuote = extras.getBoolean(ConversationsActivity.EXTRA_AS_QUOTE);
final boolean pm = extras.getBoolean(ConversationsActivity.EXTRA_IS_PRIVATE_MESSAGE, false);
- final boolean doNotAppend = extras.getBoolean(ConversationsActivity.EXTRA_DO_NOT_APPEND, false);
+ final boolean doNotAppend =
+ extras.getBoolean(ConversationsActivity.EXTRA_DO_NOT_APPEND, false);
final String type = extras.getString(ConversationsActivity.EXTRA_TYPE);
final List uris = extractUris(extras);
if (uris != null && uris.size() > 0) {
if (uris.size() == 1 && "geo".equals(uris.get(0).getScheme())) {
- mediaPreviewAdapter.addMediaPreviews(Attachment.of(getActivity(), uris.get(0), Attachment.Type.LOCATION));
+ mediaPreviewAdapter.addMediaPreviews(
+ Attachment.of(getActivity(), uris.get(0), Attachment.Type.LOCATION));
} else {
final List cleanedUris = cleanUris(new ArrayList<>(uris));
- mediaPreviewAdapter.addMediaPreviews(Attachment.of(getActivity(), cleanedUris, type));
+ mediaPreviewAdapter.addMediaPreviews(
+ Attachment.of(getActivity(), cleanedUris, type));
}
toggleInputMethod();
return;
@@ -2216,7 +2552,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
Jid next = Jid.of(jid.getLocal(), jid.getDomain(), nick);
privateMessageWith(next);
} catch (final IllegalArgumentException ignored) {
- //do nothing
+ // do nothing
}
} else {
final MucOptions mucOptions = conversation.getMucOptions();
@@ -2226,7 +2562,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
} else {
if (text != null && GeoHelper.GEO_URI.matcher(text).matches()) {
- mediaPreviewAdapter.addMediaPreviews(Attachment.of(getActivity(), Uri.parse(text), Attachment.Type.LOCATION));
+ mediaPreviewAdapter.addMediaPreviews(
+ Attachment.of(getActivity(), Uri.parse(text), Attachment.Type.LOCATION));
toggleInputMethod();
return;
} else if (text != null && asQuote) {
@@ -2239,7 +2576,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
attachFile(ATTACHMENT_CHOICE_RECORD_VOICE, false);
return;
}
- final Message message = downloadUuid == null ? null : conversation.findMessageWithFileAndUuid(downloadUuid);
+ final Message message =
+ downloadUuid == null ? null : conversation.findMessageWithFileAndUuid(downloadUuid);
if (message != null) {
startDownloadable(message);
}
@@ -2264,7 +2602,11 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
final Uri uri = iterator.next();
if (FileBackend.weOwnFile(uri)) {
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();
}
}
return uris;
@@ -2272,27 +2614,37 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
private boolean showBlockSubmenu(View view) {
final Jid jid = conversation.getJid();
- final boolean showReject = !conversation.isWithStranger() && conversation.getContact().getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST);
+ final boolean showReject =
+ !conversation.isWithStranger()
+ && conversation
+ .getContact()
+ .getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST);
PopupMenu popupMenu = new PopupMenu(getActivity(), view);
popupMenu.inflate(R.menu.block);
popupMenu.getMenu().findItem(R.id.block_contact).setVisible(jid.getLocal() != null);
popupMenu.getMenu().findItem(R.id.reject).setVisible(showReject);
- popupMenu.setOnMenuItemClickListener(menuItem -> {
- Blockable blockable;
- switch (menuItem.getItemId()) {
- case R.id.reject:
- activity.xmppConnectionService.stopPresenceUpdatesTo(conversation.getContact());
- updateSnackBar(conversation);
+ popupMenu.setOnMenuItemClickListener(
+ menuItem -> {
+ Blockable blockable;
+ switch (menuItem.getItemId()) {
+ case R.id.reject:
+ activity.xmppConnectionService.stopPresenceUpdatesTo(
+ conversation.getContact());
+ updateSnackBar(conversation);
+ return true;
+ case R.id.block_domain:
+ blockable =
+ conversation
+ .getAccount()
+ .getRoster()
+ .getContact(jid.getDomain());
+ break;
+ default:
+ blockable = conversation;
+ }
+ BlockContactDialog.show(activity, blockable);
return true;
- case R.id.block_domain:
- blockable = conversation.getAccount().getRoster().getContact(jid.getDomain());
- break;
- default:
- blockable = conversation;
- }
- BlockContactDialog.show(activity, blockable);
- return true;
- });
+ });
popupMenu.show();
return true;
}
@@ -2306,13 +2658,27 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
return;
}
if (account.getStatus() == Account.State.DISABLED) {
- showSnackbar(R.string.this_account_is_disabled, R.string.enable, this.mEnableAccountListener);
+ showSnackbar(
+ R.string.this_account_is_disabled,
+ R.string.enable,
+ this.mEnableAccountListener);
} else if (conversation.isBlocked()) {
showSnackbar(R.string.contact_blocked, R.string.unblock, this.mUnblockClickListener);
- } else if (contact != null && !contact.showInRoster() && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
- showSnackbar(R.string.contact_added_you, R.string.add_back, this.mAddBackClickListener, this.mLongPressBlockListener);
- } else if (contact != null && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
- showSnackbar(R.string.contact_asks_for_presence_subscription, R.string.allow, this.mAllowPresenceSubscription, this.mLongPressBlockListener);
+ } else if (contact != null
+ && !contact.showInRoster()
+ && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
+ showSnackbar(
+ R.string.contact_added_you,
+ R.string.add_back,
+ this.mAddBackClickListener,
+ this.mLongPressBlockListener);
+ } else if (contact != null
+ && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
+ showSnackbar(
+ R.string.contact_asks_for_presence_subscription,
+ R.string.allow,
+ this.mAllowPresenceSubscription,
+ this.mLongPressBlockListener);
} else if (mode == Conversation.MODE_MULTI
&& !conversation.getMucOptions().online()
&& account.getStatus() == Account.State.ONLINE) {
@@ -2338,7 +2704,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
break;
case PASSWORD_REQUIRED:
- showSnackbar(R.string.conference_requires_password, R.string.enter_password, enterPassword);
+ showSnackbar(
+ R.string.conference_requires_password,
+ R.string.enter_password,
+ enterPassword);
break;
case BANNED:
showSnackbar(R.string.conference_banned, R.string.leave, leaveMuc);
@@ -2347,7 +2716,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
showSnackbar(R.string.conference_members_only, R.string.leave, leaveMuc);
break;
case RESOURCE_CONSTRAINT:
- showSnackbar(R.string.conference_resource_constraint, R.string.try_again, joinMuc);
+ showSnackbar(
+ R.string.conference_resource_constraint, R.string.try_again, joinMuc);
break;
case KICKED:
showSnackbar(R.string.conference_kicked, R.string.join, joinMuc);
@@ -2364,7 +2734,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
showSnackbar(R.string.conference_destroyed, R.string.leave, leaveMuc);
break;
case NON_ANONYMOUS:
- showSnackbar(R.string.group_chat_will_make_your_jabber_id_public, R.string.join, acceptJoin);
+ showSnackbar(
+ R.string.group_chat_will_make_your_jabber_id_public,
+ R.string.join,
+ acceptJoin);
break;
default:
hideSnackbar();
@@ -2377,7 +2750,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
&& conversation.countMessages() != 0
&& !conversation.isBlocked()
&& conversation.isWithStranger()) {
- showSnackbar(R.string.received_message_from_stranger, R.string.block, mBlockClickListener);
+ showSnackbar(
+ R.string.received_message_from_stranger, R.string.block, mBlockClickListener);
} else {
hideSnackbar();
}
@@ -2386,10 +2760,14 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
@Override
public void refresh() {
if (this.binding == null) {
- Log.d(Config.LOGTAG, "ConversationFragment.refresh() skipped updated because view binding was null");
+ Log.d(
+ Config.LOGTAG,
+ "ConversationFragment.refresh() skipped updated because view binding was null");
return;
}
- if (this.conversation != null && this.activity != null && this.activity.xmppConnectionService != null) {
+ if (this.conversation != null
+ && this.activity != null
+ && this.activity.xmppConnectionService != null) {
if (!activity.xmppConnectionService.isConversationStillOpen(this.conversation)) {
activity.onConversationArchived(this.conversation);
return;
@@ -2406,7 +2784,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
updateStatusMessages();
if (conversation.getReceivedMessagesCountSinceUuid(lastMessageUuid) != 0) {
binding.unreadCountCustomView.setVisibility(View.VISIBLE);
- binding.unreadCountCustomView.setUnreadCount(conversation.getReceivedMessagesCountSinceUuid(lastMessageUuid));
+ binding.unreadCountCustomView.setUnreadCount(
+ conversation.getReceivedMessagesCountSinceUuid(lastMessageUuid));
}
this.messageListAdapter.notifyDataSetChanged();
updateChatMsgHint();
@@ -2429,12 +2808,17 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
storeNextMessage();
updateChatMsgHint();
SharedPreferences p = PreferenceManager.getDefaultSharedPreferences(activity);
- final boolean prefScrollToBottom = p.getBoolean("scroll_to_bottom", activity.getResources().getBoolean(R.bool.scroll_to_bottom));
+ final boolean prefScrollToBottom =
+ p.getBoolean(
+ "scroll_to_bottom",
+ activity.getResources().getBoolean(R.bool.scroll_to_bottom));
if (prefScrollToBottom || scrolledToBottom()) {
- new Handler().post(() -> {
- int size = messageList.size();
- this.binding.messagesView.setSelection(size - 1);
- });
+ new Handler()
+ .post(
+ () -> {
+ int size = messageList.size();
+ this.binding.messagesView.setSelection(size - 1);
+ });
}
}
@@ -2443,8 +2827,12 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
private boolean storeNextMessage(String msg) {
- final boolean participating = conversation.getMode() == Conversational.MODE_SINGLE || conversation.getMucOptions().participating();
- if (this.conversation.getStatus() != Conversation.STATUS_ARCHIVED && participating && this.conversation.setNextMessage(msg)) {
+ final boolean participating =
+ conversation.getMode() == Conversational.MODE_SINGLE
+ || conversation.getMucOptions().participating();
+ if (this.conversation.getStatus() != Conversation.STATUS_ARCHIVED
+ && participating
+ && this.conversation.setNextMessage(msg)) {
this.activity.xmppConnectionService.updateConversation(this.conversation);
return true;
}
@@ -2461,7 +2849,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
private void updateEditablity() {
- boolean canWrite = this.conversation.getMode() == Conversation.MODE_SINGLE || this.conversation.getMucOptions().participating() || this.conversation.getNextCounterpart() != null;
+ boolean canWrite =
+ this.conversation.getMode() == Conversation.MODE_SINGLE
+ || this.conversation.getMucOptions().participating()
+ || this.conversation.getNextCounterpart() != null;
this.binding.textinput.setFocusable(canWrite);
this.binding.textinput.setFocusableInTouchMode(canWrite);
this.binding.textSendButton.setEnabled(canWrite);
@@ -2470,10 +2861,12 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
public void updateSendButton() {
- boolean hasAttachments = mediaPreviewAdapter != null && mediaPreviewAdapter.hasAttachments();
+ boolean hasAttachments =
+ mediaPreviewAdapter != null && mediaPreviewAdapter.hasAttachments();
final Conversation c = this.conversation;
final Presence.Status status;
- final String text = this.binding.textinput == null ? "" : this.binding.textinput.getText().toString();
+ final String text =
+ this.binding.textinput == null ? "" : this.binding.textinput.getText().toString();
final SendButtonAction action;
if (hasAttachments) {
action = SendButtonAction.TEXT;
@@ -2481,12 +2874,17 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
action = SendButtonTool.getAction(getActivity(), c, text);
}
if (c.getAccount().getStatus() == Account.State.ONLINE) {
- if (activity != null && activity.xmppConnectionService != null && activity.xmppConnectionService.getMessageArchiveService().isCatchingUp(c)) {
+ if (activity != null
+ && activity.xmppConnectionService != null
+ && activity.xmppConnectionService.getMessageArchiveService().isCatchingUp(c)) {
status = Presence.Status.OFFLINE;
} else if (c.getMode() == Conversation.MODE_SINGLE) {
status = c.getContact().getShownStatus();
} else {
- status = c.getMucOptions().online() ? Presence.Status.ONLINE : Presence.Status.OFFLINE;
+ status =
+ c.getMucOptions().online()
+ ? Presence.Status.ONLINE
+ : Presence.Status.OFFLINE;
}
} else {
status = Presence.Status.OFFLINE;
@@ -2494,7 +2892,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
this.binding.textSendButton.setTag(action);
final Activity activity = getActivity();
if (activity != null) {
- this.binding.textSendButton.setImageResource(SendButtonTool.getSendButtonImageResource(activity, action, status));
+ this.binding.textSendButton.setImageResource(
+ SendButtonTool.getSendButtonImageResource(activity, action, status));
}
}
@@ -2506,9 +2905,17 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
if (conversation.getMode() == Conversation.MODE_SINGLE) {
ChatState state = conversation.getIncomingChatState();
if (state == ChatState.COMPOSING) {
- this.messageList.add(Message.createStatusMessage(conversation, getString(R.string.contact_is_typing, conversation.getName())));
+ this.messageList.add(
+ Message.createStatusMessage(
+ conversation,
+ getString(R.string.contact_is_typing, conversation.getName())));
} else if (state == ChatState.PAUSED) {
- this.messageList.add(Message.createStatusMessage(conversation, getString(R.string.contact_has_stopped_typing, conversation.getName())));
+ this.messageList.add(
+ Message.createStatusMessage(
+ conversation,
+ getString(
+ R.string.contact_has_stopped_typing,
+ conversation.getName())));
} else {
for (int i = this.messageList.size() - 1; i >= 0; --i) {
final Message message = this.messageList.get(i);
@@ -2517,8 +2924,13 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
return;
} else {
if (message.getStatus() == Message.STATUS_SEND_DISPLAYED) {
- this.messageList.add(i + 1,
- Message.createStatusMessage(conversation, getString(R.string.contact_has_read_up_to_this_point, conversation.getName())));
+ this.messageList.add(
+ i + 1,
+ Message.createStatusMessage(
+ conversation,
+ getString(
+ R.string.contact_has_read_up_to_this_point,
+ conversation.getName())));
return;
}
}
@@ -2530,18 +2942,22 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
final List allUsers = mucOptions.getUsers();
final Set addedMarkers = new HashSet<>();
ChatState state = ChatState.COMPOSING;
- List users = conversation.getMucOptions().getUsersWithChatState(state, 5);
+ List users =
+ conversation.getMucOptions().getUsersWithChatState(state, 5);
if (users.size() == 0) {
state = ChatState.PAUSED;
users = conversation.getMucOptions().getUsersWithChatState(state, 5);
}
if (mucOptions.isPrivateAndNonAnonymous()) {
for (int i = this.messageList.size() - 1; i >= 0; --i) {
- final Set markersForMessage = messageList.get(i).getReadByMarkers();
+ final Set markersForMessage =
+ messageList.get(i).getReadByMarkers();
final List shownMarkers = new ArrayList<>();
for (ReadByMarker marker : markersForMessage) {
if (!ReadByMarker.contains(marker, addedMarkers)) {
- addedMarkers.add(marker); //may be put outside this condition. set should do dedup anyway
+ addedMarkers.add(
+ marker); // may be put outside this condition. set should do
+ // dedup anyway
MucOptions.User user = mucOptions.findUser(marker);
if (user != null && !users.contains(user)) {
shownMarkers.add(user);
@@ -2554,16 +2970,29 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
if (size > 1) {
final String body;
if (size <= 4) {
- body = getString(R.string.contacts_have_read_up_to_this_point, UIHelper.concatNames(shownMarkers));
- } else if (ReadByMarker.allUsersRepresented(allUsers, markersForMessage, markerForSender)) {
+ body =
+ getString(
+ R.string.contacts_have_read_up_to_this_point,
+ UIHelper.concatNames(shownMarkers));
+ } else if (ReadByMarker.allUsersRepresented(
+ allUsers, markersForMessage, markerForSender)) {
body = getString(R.string.everyone_has_read_up_to_this_point);
} else {
- body = getString(R.string.contacts_and_n_more_have_read_up_to_this_point, UIHelper.concatNames(shownMarkers, 3), size - 3);
+ body =
+ getString(
+ R.string.contacts_and_n_more_have_read_up_to_this_point,
+ UIHelper.concatNames(shownMarkers, 3),
+ size - 3);
}
statusMessage = Message.createStatusMessage(conversation, body);
statusMessage.setCounterparts(shownMarkers);
} else if (size == 1) {
- statusMessage = Message.createStatusMessage(conversation, getString(R.string.contact_has_read_up_to_this_point, UIHelper.getDisplayName(shownMarkers.get(0))));
+ statusMessage =
+ Message.createStatusMessage(
+ conversation,
+ getString(
+ R.string.contact_has_read_up_to_this_point,
+ UIHelper.getDisplayName(shownMarkers.get(0))));
statusMessage.setCounterpart(shownMarkers.get(0).getFullJid());
statusMessage.setTrueCounterpart(shownMarkers.get(0).getRealJid());
} else {
@@ -2582,18 +3011,27 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
Message statusMessage;
if (users.size() == 1) {
MucOptions.User user = users.get(0);
- int id = state == ChatState.COMPOSING ? R.string.contact_is_typing : R.string.contact_has_stopped_typing;
- statusMessage = Message.createStatusMessage(conversation, getString(id, UIHelper.getDisplayName(user)));
+ int id =
+ state == ChatState.COMPOSING
+ ? R.string.contact_is_typing
+ : R.string.contact_has_stopped_typing;
+ statusMessage =
+ Message.createStatusMessage(
+ conversation, getString(id, UIHelper.getDisplayName(user)));
statusMessage.setTrueCounterpart(user.getRealJid());
statusMessage.setCounterpart(user.getFullJid());
} else {
- int id = state == ChatState.COMPOSING ? R.string.contacts_are_typing : R.string.contacts_have_stopped_typing;
- statusMessage = Message.createStatusMessage(conversation, getString(id, UIHelper.concatNames(users)));
+ int id =
+ state == ChatState.COMPOSING
+ ? R.string.contacts_are_typing
+ : R.string.contacts_have_stopped_typing;
+ statusMessage =
+ Message.createStatusMessage(
+ conversation, getString(id, UIHelper.concatNames(users)));
statusMessage.setCounterparts(users);
}
this.messageList.add(statusMessage);
}
-
}
}
@@ -2608,8 +3046,14 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
return false;
}
final boolean mam = hasMamSupport(c) && !c.getContact().isBlocked();
- final MessageArchiveService service = activity.xmppConnectionService.getMessageArchiveService();
- return mam && (c.getLastClearHistory().getTimestamp() != 0 || (c.countMessages() == 0 && c.messagesLoaded.get() && c.hasMessagesLeftOnServer() && !service.queryInProgress(c)));
+ final MessageArchiveService service =
+ activity.xmppConnectionService.getMessageArchiveService();
+ return mam
+ && (c.getLastClearHistory().getTimestamp() != 0
+ || (c.countMessages() == 0
+ && c.messagesLoaded.get()
+ && c.hasMessagesLeftOnServer()
+ && !service.queryInProgress(c)));
}
private boolean hasMamSupport(final Conversation c) {
@@ -2621,11 +3065,16 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
}
- protected void showSnackbar(final int message, final int action, final OnClickListener clickListener) {
+ protected void showSnackbar(
+ final int message, final int action, final OnClickListener clickListener) {
showSnackbar(message, action, clickListener, null);
}
- protected void showSnackbar(final int message, final int action, final OnClickListener clickListener, final View.OnLongClickListener longClickListener) {
+ protected void showSnackbar(
+ final int message,
+ final int action,
+ final OnClickListener clickListener,
+ final View.OnLongClickListener longClickListener) {
this.binding.snackbar.setVisibility(View.VISIBLE);
this.binding.snackbar.setOnClickListener(null);
this.binding.snackbarMessage.setText(message);
@@ -2655,7 +3104,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
return;
}
if (conversation.getAccount().getPgpSignature() == null) {
- activity.announcePgp(conversation.getAccount(), conversation, null, activity.onOpenPGPKeyPublished);
+ activity.announcePgp(
+ conversation.getAccount(), conversation, null, activity.onOpenPGPKeyPublished);
return;
}
if (!mSendingPgpMessage.compareAndSet(false, true)) {
@@ -2663,85 +3113,107 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
if (conversation.getMode() == Conversation.MODE_SINGLE) {
if (contact.getPgpKeyId() != 0) {
- xmppService.getPgpEngine().hasKey(contact,
- new UiCallback() {
-
- @Override
- public void userInputRequired(PendingIntent pi, Contact contact) {
- startPendingIntent(pi, REQUEST_ENCRYPT_MESSAGE);
- }
+ xmppService
+ .getPgpEngine()
+ .hasKey(
+ contact,
+ new UiCallback() {
+
+ @Override
+ public void userInputRequired(
+ PendingIntent pi, Contact contact) {
+ startPendingIntent(pi, REQUEST_ENCRYPT_MESSAGE);
+ }
- @Override
- public void success(Contact contact) {
- encryptTextMessage(message);
- }
+ @Override
+ public void success(Contact contact) {
+ encryptTextMessage(message);
+ }
- @Override
- public void error(int error, Contact contact) {
- activity.runOnUiThread(() -> Toast.makeText(activity,
- R.string.unable_to_connect_to_keychain,
- Toast.LENGTH_SHORT
- ).show());
- mSendingPgpMessage.set(false);
- }
- });
+ @Override
+ public void error(int error, Contact contact) {
+ activity.runOnUiThread(
+ () ->
+ Toast.makeText(
+ activity,
+ R.string
+ .unable_to_connect_to_keychain,
+ Toast.LENGTH_SHORT)
+ .show());
+ mSendingPgpMessage.set(false);
+ }
+ });
} else {
- showNoPGPKeyDialog(false, (dialog, which) -> {
- conversation.setNextEncryption(Message.ENCRYPTION_NONE);
- xmppService.updateConversation(conversation);
- message.setEncryption(Message.ENCRYPTION_NONE);
- xmppService.sendMessage(message);
- messageSent();
- });
+ showNoPGPKeyDialog(
+ false,
+ (dialog, which) -> {
+ conversation.setNextEncryption(Message.ENCRYPTION_NONE);
+ xmppService.updateConversation(conversation);
+ message.setEncryption(Message.ENCRYPTION_NONE);
+ xmppService.sendMessage(message);
+ messageSent();
+ });
}
} else {
if (conversation.getMucOptions().pgpKeysInUse()) {
if (!conversation.getMucOptions().everybodyHasKeys()) {
- Toast warning = Toast
- .makeText(getActivity(),
- R.string.missing_public_keys,
- Toast.LENGTH_LONG);
+ Toast warning =
+ Toast.makeText(
+ getActivity(), R.string.missing_public_keys, Toast.LENGTH_LONG);
warning.setGravity(Gravity.CENTER_VERTICAL, 0, 0);
warning.show();
}
encryptTextMessage(message);
} else {
- showNoPGPKeyDialog(true, (dialog, which) -> {
- conversation.setNextEncryption(Message.ENCRYPTION_NONE);
- message.setEncryption(Message.ENCRYPTION_NONE);
- xmppService.updateConversation(conversation);
- xmppService.sendMessage(message);
- messageSent();
- });
+ showNoPGPKeyDialog(
+ true,
+ (dialog, which) -> {
+ conversation.setNextEncryption(Message.ENCRYPTION_NONE);
+ message.setEncryption(Message.ENCRYPTION_NONE);
+ xmppService.updateConversation(conversation);
+ xmppService.sendMessage(message);
+ messageSent();
+ });
}
}
}
public void encryptTextMessage(Message message) {
- activity.xmppConnectionService.getPgpEngine().encrypt(message,
- new UiCallback() {
+ activity.xmppConnectionService
+ .getPgpEngine()
+ .encrypt(
+ message,
+ new UiCallback() {
- @Override
- public void userInputRequired(PendingIntent pi, Message message) {
- startPendingIntent(pi, REQUEST_SEND_MESSAGE);
- }
+ @Override
+ public void userInputRequired(PendingIntent pi, Message message) {
+ startPendingIntent(pi, REQUEST_SEND_MESSAGE);
+ }
- @Override
- public void success(Message message) {
- //TODO the following two call can be made before the callback
- getActivity().runOnUiThread(() -> messageSent());
- }
+ @Override
+ public void success(Message message) {
+ // TODO the following two call can be made before the callback
+ getActivity().runOnUiThread(() -> messageSent());
+ }
- @Override
- public void error(final int error, Message message) {
- getActivity().runOnUiThread(() -> {
- doneSendingPgpMessage();
- Toast.makeText(getActivity(), error == 0 ? R.string.unable_to_connect_to_keychain : error, Toast.LENGTH_SHORT).show();
+ @Override
+ public void error(final int error, Message message) {
+ getActivity()
+ .runOnUiThread(
+ () -> {
+ doneSendingPgpMessage();
+ Toast.makeText(
+ getActivity(),
+ error == 0
+ ? R.string
+ .unable_to_connect_to_keychain
+ : error,
+ Toast.LENGTH_SHORT)
+ .show();
+ });
+ }
});
-
- }
- });
}
public void showNoPGPKeyDialog(boolean plural, DialogInterface.OnClickListener listener) {
@@ -2766,12 +3238,14 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
final Editable editable = this.binding.textinput.getText();
String previous = editable == null ? "" : editable.toString();
if (doNotAppend && !TextUtils.isEmpty(previous)) {
- Toast.makeText(getActivity(), R.string.already_drafting_message, Toast.LENGTH_LONG).show();
+ Toast.makeText(getActivity(), R.string.already_drafting_message, Toast.LENGTH_LONG)
+ .show();
return;
}
if (UIHelper.isLastLineQuote(previous)) {
text = '\n' + text;
- } else if (previous.length() != 0 && !Character.isWhitespace(previous.charAt(previous.length() - 1))) {
+ } else if (previous.length() != 0
+ && !Character.isWhitespace(previous.charAt(previous.length() - 1))) {
text = " " + text;
}
this.binding.textinput.append(text);
@@ -2792,24 +3266,28 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
}
public boolean onArrowUpCtrlPressed() {
- final Message lastEditableMessage = conversation == null ? null : conversation.getLastEditableMessage();
+ final Message lastEditableMessage =
+ conversation == null ? null : conversation.getLastEditableMessage();
if (lastEditableMessage != null) {
correctMessage(lastEditableMessage);
return true;
} else {
- Toast.makeText(getActivity(), R.string.could_not_correct_message, Toast.LENGTH_LONG).show();
+ Toast.makeText(getActivity(), R.string.could_not_correct_message, Toast.LENGTH_LONG)
+ .show();
return false;
}
}
@Override
public void onTypingStarted() {
- final XmppConnectionService service = activity == null ? null : activity.xmppConnectionService;
+ final XmppConnectionService service =
+ activity == null ? null : activity.xmppConnectionService;
if (service == null) {
return;
}
final Account.State status = conversation.getAccount().getStatus();
- if (status == Account.State.ONLINE && conversation.setOutgoingChatState(ChatState.COMPOSING)) {
+ if (status == Account.State.ONLINE
+ && conversation.setOutgoingChatState(ChatState.COMPOSING)) {
service.sendChatState(conversation);
}
runOnUiThread(this::updateSendButton);
@@ -2817,7 +3295,8 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
@Override
public void onTypingStopped() {
- final XmppConnectionService service = activity == null ? null : activity.xmppConnectionService;
+ final XmppConnectionService service =
+ activity == null ? null : activity.xmppConnectionService;
if (service == null) {
return;
}
@@ -2829,21 +3308,24 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
@Override
public void onTextDeleted() {
- final XmppConnectionService service = activity == null ? null : activity.xmppConnectionService;
+ final XmppConnectionService service =
+ activity == null ? null : activity.xmppConnectionService;
if (service == null) {
return;
}
final Account.State status = conversation.getAccount().getStatus();
- if (status == Account.State.ONLINE && conversation.setOutgoingChatState(Config.DEFAULT_CHAT_STATE)) {
+ if (status == Account.State.ONLINE
+ && conversation.setOutgoingChatState(Config.DEFAULT_CHAT_STATE)) {
service.sendChatState(conversation);
}
if (storeNextMessage()) {
- runOnUiThread(() -> {
- if (activity == null) {
- return;
- }
- activity.onConversationsListItemUpdated();
- });
+ runOnUiThread(
+ () -> {
+ if (activity == null) {
+ return;
+ }
+ activity.onConversationsListItemUpdated();
+ });
}
runOnUiThread(this::updateSendButton);
}
@@ -2867,7 +3349,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
completionIndex = 0;
final String content = this.binding.textinput.getText().toString();
lastCompletionCursor = this.binding.textinput.getSelectionEnd();
- int start = lastCompletionCursor > 0 ? content.lastIndexOf(" ", lastCompletionCursor - 1) + 1 : 0;
+ int start =
+ lastCompletionCursor > 0
+ ? content.lastIndexOf(" ", lastCompletionCursor - 1) + 1
+ : 0;
firstWord = start == 0;
incomplete = content.substring(start, lastCompletionCursor);
}
@@ -2881,12 +3366,18 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
Collections.sort(completions);
if (completions.size() > completionIndex) {
String completion = completions.get(completionIndex).substring(incomplete.length());
- this.binding.textinput.getEditableText().delete(lastCompletionCursor, lastCompletionCursor + lastCompletionLength);
+ this.binding
+ .textinput
+ .getEditableText()
+ .delete(lastCompletionCursor, lastCompletionCursor + lastCompletionLength);
this.binding.textinput.getEditableText().insert(lastCompletionCursor, completion);
lastCompletionLength = completion.length();
} else {
completionIndex = -1;
- this.binding.textinput.getEditableText().delete(lastCompletionCursor, lastCompletionCursor + lastCompletionLength);
+ this.binding
+ .textinput
+ .getEditableText()
+ .delete(lastCompletionCursor, lastCompletionCursor + lastCompletionLength);
lastCompletionLength = 0;
}
return true;
@@ -2894,7 +3385,9 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
private void startPendingIntent(PendingIntent pendingIntent, int requestCode) {
try {
- getActivity().startIntentSenderForResult(pendingIntent.getIntentSender(), requestCode, null, 0, 0, 0);
+ getActivity()
+ .startIntentSenderForResult(
+ pendingIntent.getIntentSender(), requestCode, null, 0, 0, 0);
} catch (final SendIntentException ignored) {
}
}
@@ -2968,63 +3461,85 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
@Override
public void onContactPictureLongClicked(View v, final Message message) {
final String fingerprint;
- if (message.getEncryption() == Message.ENCRYPTION_PGP || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
+ if (message.getEncryption() == Message.ENCRYPTION_PGP
+ || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
fingerprint = "pgp";
} else {
fingerprint = message.getFingerprint();
}
final PopupMenu popupMenu = new PopupMenu(getActivity(), v);
final Contact contact = message.getContact();
- if (message.getStatus() <= Message.STATUS_RECEIVED && (contact == null || !contact.isSelf())) {
+ if (message.getStatus() <= Message.STATUS_RECEIVED
+ && (contact == null || !contact.isSelf())) {
if (message.getConversation().getMode() == Conversation.MODE_MULTI) {
final Jid cp = message.getCounterpart();
if (cp == null || cp.isBareJid()) {
return;
}
final Jid tcp = message.getTrueCounterpart();
- final User userByRealJid = tcp != null ? conversation.getMucOptions().findOrCreateUserByRealJid(tcp, cp) : null;
- final User user = userByRealJid != null ? userByRealJid : conversation.getMucOptions().findUserByFullJid(cp);
+ final User userByRealJid =
+ tcp != null
+ ? conversation.getMucOptions().findOrCreateUserByRealJid(tcp, cp)
+ : null;
+ final User user =
+ userByRealJid != null
+ ? userByRealJid
+ : conversation.getMucOptions().findUserByFullJid(cp);
popupMenu.inflate(R.menu.muc_details_context);
final Menu menu = popupMenu.getMenu();
- MucDetailsContextMenuHelper.configureMucDetailsContextMenu(activity, menu, conversation, user);
- popupMenu.setOnMenuItemClickListener(menuItem -> MucDetailsContextMenuHelper.onContextItemSelected(menuItem, user, activity, fingerprint));
+ MucDetailsContextMenuHelper.configureMucDetailsContextMenu(
+ activity, menu, conversation, user);
+ popupMenu.setOnMenuItemClickListener(
+ menuItem ->
+ MucDetailsContextMenuHelper.onContextItemSelected(
+ menuItem, user, activity, fingerprint));
} else {
popupMenu.inflate(R.menu.one_on_one_context);
- popupMenu.setOnMenuItemClickListener(item -> {
- switch (item.getItemId()) {
- case R.id.action_contact_details:
- activity.switchToContactDetails(message.getContact(), fingerprint);
- break;
- case R.id.action_show_qr_code:
- activity.showQrCode("xmpp:" + message.getContact().getJid().asBareJid().toEscapedString());
- break;
- }
- return true;
- });
+ popupMenu.setOnMenuItemClickListener(
+ item -> {
+ switch (item.getItemId()) {
+ case R.id.action_contact_details:
+ activity.switchToContactDetails(
+ message.getContact(), fingerprint);
+ break;
+ case R.id.action_show_qr_code:
+ activity.showQrCode(
+ "xmpp:"
+ + message.getContact()
+ .getJid()
+ .asBareJid()
+ .toEscapedString());
+ break;
+ }
+ return true;
+ });
}
} else {
popupMenu.inflate(R.menu.account_context);
final Menu menu = popupMenu.getMenu();
- menu.findItem(R.id.action_manage_accounts).setVisible(QuickConversationsService.isConversations());
- popupMenu.setOnMenuItemClickListener(item -> {
- final XmppActivity activity = this.activity;
- if (activity == null) {
- Log.e(Config.LOGTAG,"Unable to perform action. no context provided");
- return true;
- }
- switch (item.getItemId()) {
- case R.id.action_show_qr_code:
- activity.showQrCode(conversation.getAccount().getShareableUri());
- break;
- case R.id.action_account_details:
- activity.switchToAccount(message.getConversation().getAccount(), fingerprint);
- break;
- case R.id.action_manage_accounts:
- AccountUtils.launchManageAccounts(activity);
- break;
- }
- return true;
- });
+ menu.findItem(R.id.action_manage_accounts)
+ .setVisible(QuickConversationsService.isConversations());
+ popupMenu.setOnMenuItemClickListener(
+ item -> {
+ final XmppActivity activity = this.activity;
+ if (activity == null) {
+ Log.e(Config.LOGTAG, "Unable to perform action. no context provided");
+ return true;
+ }
+ switch (item.getItemId()) {
+ case R.id.action_show_qr_code:
+ activity.showQrCode(conversation.getAccount().getShareableUri());
+ break;
+ case R.id.action_account_details:
+ activity.switchToAccount(
+ message.getConversation().getAccount(), fingerprint);
+ break;
+ case R.id.action_manage_accounts:
+ AccountUtils.launchManageAccounts(activity);
+ break;
+ }
+ return true;
+ });
}
popupMenu.show();
}
@@ -3032,25 +3547,43 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
@Override
public void onContactPictureClicked(Message message) {
String fingerprint;
- if (message.getEncryption() == Message.ENCRYPTION_PGP || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
+ if (message.getEncryption() == Message.ENCRYPTION_PGP
+ || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
fingerprint = "pgp";
} else {
fingerprint = message.getFingerprint();
}
final boolean received = message.getStatus() <= Message.STATUS_RECEIVED;
if (received) {
- if (message.getConversation() instanceof Conversation && message.getConversation().getMode() == Conversation.MODE_MULTI) {
+ if (message.getConversation() instanceof Conversation
+ && message.getConversation().getMode() == Conversation.MODE_MULTI) {
Jid tcp = message.getTrueCounterpart();
Jid user = message.getCounterpart();
if (user != null && !user.isBareJid()) {
- final MucOptions mucOptions = ((Conversation) message.getConversation()).getMucOptions();
- if (mucOptions.participating() || ((Conversation) message.getConversation()).getNextCounterpart() != null) {
- if (!mucOptions.isUserInRoom(user) && mucOptions.findUserByRealJid(tcp == null ? null : tcp.asBareJid()) == null) {
- Toast.makeText(getActivity(), activity.getString(R.string.user_has_left_conference, user.getResource()), Toast.LENGTH_SHORT).show();
+ final MucOptions mucOptions =
+ ((Conversation) message.getConversation()).getMucOptions();
+ if (mucOptions.participating()
+ || ((Conversation) message.getConversation()).getNextCounterpart()
+ != null) {
+ if (!mucOptions.isUserInRoom(user)
+ && mucOptions.findUserByRealJid(
+ tcp == null ? null : tcp.asBareJid())
+ == null) {
+ Toast.makeText(
+ getActivity(),
+ activity.getString(
+ R.string.user_has_left_conference,
+ user.getResource()),
+ Toast.LENGTH_SHORT)
+ .show();
}
highlightInConference(user.getResource());
} else {
- Toast.makeText(getActivity(), R.string.you_are_not_participating, Toast.LENGTH_SHORT).show();
+ Toast.makeText(
+ getActivity(),
+ R.string.you_are_not_participating,
+ Toast.LENGTH_SHORT)
+ .show();
}
}
return;
diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java
index b9abae46baf3c3a95677dafb94a09b6b3e18382b..8368ae7466bb240ca82f38ea81f2ec8ce1ab825a 100644
--- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java
@@ -304,14 +304,16 @@ public class RtpSessionActivity extends XmppActivity
}
private void requestPermissionsAndAcceptCall() {
- final List permissions;
+ final ImmutableList.Builder permissions = ImmutableList.builder();
if (getMedia().contains(Media.VIDEO)) {
- permissions =
- ImmutableList.of(Manifest.permission.CAMERA, Manifest.permission.RECORD_AUDIO);
+ permissions.add(Manifest.permission.CAMERA).add(Manifest.permission.RECORD_AUDIO);
} else {
- permissions = ImmutableList.of(Manifest.permission.RECORD_AUDIO);
+ permissions.add(Manifest.permission.RECORD_AUDIO);
}
- if (PermissionUtils.hasPermission(this, permissions, REQUEST_ACCEPT_CALL)) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ permissions.add(Manifest.permission.BLUETOOTH_CONNECT);
+ }
+ if (PermissionUtils.hasPermission(this, permissions.build(), REQUEST_ACCEPT_CALL)) {
putScreenInCallMode();
checkRecorderAndAcceptCall();
}
@@ -523,13 +525,16 @@ public class RtpSessionActivity extends XmppActivity
public void onRequestPermissionsResult(
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- if (PermissionUtils.allGranted(grantResults)) {
+ final PermissionUtils.PermissionResult permissionResult =
+ PermissionUtils.removeBluetoothConnect(permissions, grantResults);
+ if (PermissionUtils.allGranted(permissionResult.grantResults)) {
if (requestCode == REQUEST_ACCEPT_CALL) {
checkRecorderAndAcceptCall();
}
} else {
@StringRes int res;
- final String firstDenied = getFirstDenied(grantResults, permissions);
+ final String firstDenied =
+ getFirstDenied(permissionResult.grantResults, permissionResult.permissions);
if (Manifest.permission.RECORD_AUDIO.equals(firstDenied)) {
res = R.string.no_microphone_permission;
} else if (Manifest.permission.CAMERA.equals(firstDenied)) {
diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java
index 46922db6e582b4828643f20c3faf00995ea45a4d..b273883213c8eb347f93347aaf56a617c48197d2 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java
@@ -12,6 +12,7 @@ import androidx.databinding.DataBindingUtil;
import androidx.recyclerview.widget.RecyclerView;
import com.google.common.base.Optional;
+import com.google.common.base.Strings;
import java.util.List;
@@ -25,11 +26,13 @@ import eu.siacs.conversations.ui.XmppActivity;
import eu.siacs.conversations.ui.util.AvatarWorkerTask;
import eu.siacs.conversations.ui.util.StyledAttributes;
import eu.siacs.conversations.utils.IrregularUnicodeDetector;
+import eu.siacs.conversations.utils.MimeUtils;
import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession;
-public class ConversationAdapter extends RecyclerView.Adapter {
+public class ConversationAdapter
+ extends RecyclerView.Adapter {
private final XmppActivity activity;
private final List conversations;
@@ -40,11 +43,15 @@ public class ConversationAdapter extends RecyclerView.Adapter 0 && fileParams.height > 0) {
+ imageResource =
+ activity.getThemeResource(
+ R.attr.ic_attach_videocam,
+ R.drawable.ic_attach_videocam);
showPreviewText = false;
- break;
- case "audio":
- imageResource = activity.getThemeResource(R.attr.ic_attach_record, R.drawable.ic_attach_record);
+ } else if (fileParams.runtime > 0) {
+ imageResource =
+ activity.getThemeResource(
+ R.attr.ic_attach_record, R.drawable.ic_attach_record);
showPreviewText = false;
- break;
- default:
- imageResource = activity.getThemeResource(R.attr.ic_attach_document, R.drawable.ic_attach_document);
+ } else {
+ imageResource =
+ activity.getThemeResource(
+ R.attr.ic_attach_document,
+ R.drawable.ic_attach_document);
showPreviewText = true;
- break;
+ }
+ } else {
+ switch (Strings.nullToEmpty(mime).split("/")[0]) {
+ case "image":
+ imageResource =
+ activity.getThemeResource(
+ R.attr.ic_attach_photo, R.drawable.ic_attach_photo);
+ showPreviewText = false;
+ break;
+ case "video":
+ imageResource =
+ activity.getThemeResource(
+ R.attr.ic_attach_videocam,
+ R.drawable.ic_attach_videocam);
+ showPreviewText = false;
+ break;
+ case "audio":
+ imageResource =
+ activity.getThemeResource(
+ R.attr.ic_attach_record,
+ R.drawable.ic_attach_record);
+ showPreviewText = false;
+ break;
+ default:
+ imageResource =
+ activity.getThemeResource(
+ R.attr.ic_attach_document,
+ R.drawable.ic_attach_document);
+ showPreviewText = true;
+ break;
+ }
}
}
viewHolder.binding.conversationLastmsgImg.setImageResource(imageResource);
@@ -126,13 +175,18 @@ public class ConversationAdapter extends RecyclerView.Adapter preview = UIHelper.getMessagePreview(activity, message, viewHolder.binding.conversationLastmsg.getCurrentTextColor());
+ final Pair preview =
+ UIHelper.getMessagePreview(
+ activity,
+ message,
+ viewHolder.binding.conversationLastmsg.getCurrentTextColor());
if (showPreviewText) {
viewHolder.binding.conversationLastmsg.setText(UIHelper.shorten(preview.first));
} else {
viewHolder.binding.conversationLastmsgImg.setContentDescription(preview.first);
}
- viewHolder.binding.conversationLastmsg.setVisibility(showPreviewText ? View.VISIBLE : View.GONE);
+ viewHolder.binding.conversationLastmsg.setVisibility(
+ showPreviewText ? View.VISIBLE : View.GONE);
if (preview.second) {
if (isRead) {
viewHolder.binding.conversationLastmsg.setTypeface(null, Typeface.ITALIC);
@@ -153,7 +207,8 @@ public class ConversationAdapter extends RecyclerView.Adapter ongoingCall;
if (conversation.getMode() == Conversational.MODE_MULTI) {
ongoingCall = Optional.absent();
} else {
- ongoingCall = activity.xmppConnectionService.getJingleConnectionManager().getOngoingRtpConnection(conversation.getContact());
+ ongoingCall =
+ activity.xmppConnectionService
+ .getJingleConnectionManager()
+ .getOngoingRtpConnection(conversation.getContact());
}
if (ongoingCall.isPresent()) {
viewHolder.binding.notificationStatus.setVisibility(View.VISIBLE);
- final int ic_ongoing_call = activity.getThemeResource(R.attr.ic_ongoing_call_hint, R.drawable.ic_phone_in_talk_black_18dp);
- viewHolder.binding.notificationStatus.setImageResource(ic_ongoing_call);
+ final int ic_ongoing_call =
+ activity.getThemeResource(
+ R.attr.ic_ongoing_call_hint, R.drawable.ic_phone_in_talk_black_18dp);
+ viewHolder.binding.notificationStatus.setImageResource(ic_ongoing_call);
} else {
- final long muted_till = conversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL, 0);
+ final long muted_till =
+ conversation.getLongAttribute(Conversation.ATTRIBUTE_MUTED_TILL, 0);
if (muted_till == Long.MAX_VALUE) {
viewHolder.binding.notificationStatus.setVisibility(View.VISIBLE);
- int ic_notifications_off = activity.getThemeResource(R.attr.icon_notifications_off, R.drawable.ic_notifications_off_black_24dp);
+ int ic_notifications_off =
+ activity.getThemeResource(
+ R.attr.icon_notifications_off,
+ R.drawable.ic_notifications_off_black_24dp);
viewHolder.binding.notificationStatus.setImageResource(ic_notifications_off);
} else if (muted_till >= System.currentTimeMillis()) {
viewHolder.binding.notificationStatus.setVisibility(View.VISIBLE);
- int ic_notifications_paused = activity.getThemeResource(R.attr.icon_notifications_paused, R.drawable.ic_notifications_paused_black_24dp);
+ int ic_notifications_paused =
+ activity.getThemeResource(
+ R.attr.icon_notifications_paused,
+ R.drawable.ic_notifications_paused_black_24dp);
viewHolder.binding.notificationStatus.setImageResource(ic_notifications_paused);
} else if (conversation.alwaysNotify()) {
viewHolder.binding.notificationStatus.setVisibility(View.GONE);
} else {
viewHolder.binding.notificationStatus.setVisibility(View.VISIBLE);
- int ic_notifications_none = activity.getThemeResource(R.attr.icon_notifications_none, R.drawable.ic_notifications_none_black_24dp);
+ int ic_notifications_none =
+ activity.getThemeResource(
+ R.attr.icon_notifications_none,
+ R.drawable.ic_notifications_none_black_24dp);
viewHolder.binding.notificationStatus.setImageResource(ic_notifications_none);
}
}
@@ -202,9 +271,16 @@ public class ConversationAdapter extends RecyclerView.Adapter listener.onConversationClick(v, conversation));
}
@@ -217,7 +293,6 @@ public class ConversationAdapter extends RecyclerView.Adapter of(final Context context, List uris, final String type) {
final List attachments = new ArrayList<>();
for (final Uri uri : uris) {
+ if (uri == null) {
+ continue;
+ }
final String mime = MimeUtils.guessMimeTypeFromUriAndMime(context, uri, type);
attachments.add(new Attachment(uri, mime != null && isImage(mime) ? Type.IMAGE : Type.FILE, mime));
}
diff --git a/src/main/java/eu/siacs/conversations/utils/Compatibility.java b/src/main/java/eu/siacs/conversations/utils/Compatibility.java
index 33eb68ba84c6ef362e3e01f19b759294aae65bc2..c28b8fe29681302eabaf5efa59d8c6dbf6cc412b 100644
--- a/src/main/java/eu/siacs/conversations/utils/Compatibility.java
+++ b/src/main/java/eu/siacs/conversations/utils/Compatibility.java
@@ -1,5 +1,7 @@
package eu.siacs.conversations.utils;
+import static eu.siacs.conversations.services.EventReceiver.EXTRA_NEEDS_FOREGROUND_SERVICE;
+
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
@@ -24,23 +26,22 @@ import eu.siacs.conversations.R;
import eu.siacs.conversations.ui.SettingsActivity;
import eu.siacs.conversations.ui.SettingsFragment;
-import static eu.siacs.conversations.services.EventReceiver.EXTRA_NEEDS_FOREGROUND_SERVICE;
-
public class Compatibility {
- private static final List UNUSED_SETTINGS_POST_TWENTYSIX = Arrays.asList(
- "led",
- "notification_ringtone",
- "notification_headsup",
- "vibrate_on_notification"
- );
- private static final List UNUESD_SETTINGS_PRE_TWENTYSIX = Collections.singletonList(
- "message_notification_settings"
- );
-
+ private static final List UNUSED_SETTINGS_POST_TWENTYSIX =
+ Arrays.asList(
+ "led",
+ "notification_ringtone",
+ "notification_headsup",
+ "vibrate_on_notification");
+ private static final List UNUSED_SETTINGS_PRE_TWENTYSIX =
+ Collections.singletonList("message_notification_settings");
public static boolean hasStoragePermission(Context context) {
- return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ContextCompat.checkSelfPermission(context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
+ return Build.VERSION.SDK_INT < Build.VERSION_CODES.M
+ || ContextCompat.checkSelfPermission(
+ context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE)
+ == PackageManager.PERMISSION_GRANTED;
}
public static boolean s() {
@@ -70,20 +71,22 @@ public class Compatibility {
private static boolean targetsTwentySix(Context context) {
try {
final PackageManager packageManager = context.getPackageManager();
- final ApplicationInfo applicationInfo = packageManager.getApplicationInfo(context.getPackageName(), 0);
+ final ApplicationInfo applicationInfo =
+ packageManager.getApplicationInfo(context.getPackageName(), 0);
return applicationInfo == null || applicationInfo.targetSdkVersion >= 26;
} catch (PackageManager.NameNotFoundException | RuntimeException e) {
- return true; //when in doubt…
+ return true; // when in doubt…
}
}
private static boolean targetsTwentyFour(Context context) {
try {
final PackageManager packageManager = context.getPackageManager();
- final ApplicationInfo applicationInfo = packageManager.getApplicationInfo(context.getPackageName(), 0);
+ final ApplicationInfo applicationInfo =
+ packageManager.getApplicationInfo(context.getPackageName(), 0);
return applicationInfo == null || applicationInfo.targetSdkVersion >= 24;
} catch (PackageManager.NameNotFoundException | RuntimeException e) {
- return true; //when in doubt…
+ return true; // when in doubt…
}
}
@@ -96,14 +99,23 @@ public class Compatibility {
}
public static boolean keepForegroundService(Context context) {
- return runsAndTargetsTwentySix(context) || getBooleanPreference(context, SettingsActivity.KEEP_FOREGROUND_SERVICE, R.bool.enable_foreground_service);
+ return runsAndTargetsTwentySix(context)
+ || getBooleanPreference(
+ context,
+ SettingsActivity.KEEP_FOREGROUND_SERVICE,
+ R.bool.enable_foreground_service);
}
public static void removeUnusedPreferences(SettingsFragment settingsFragment) {
- List categories = Arrays.asList(
- (PreferenceCategory) settingsFragment.findPreference("notification_category"),
- (PreferenceCategory) settingsFragment.findPreference("advanced"));
- for (String key : (runsTwentySix() ? UNUSED_SETTINGS_POST_TWENTYSIX : UNUESD_SETTINGS_PRE_TWENTYSIX)) {
+ List categories =
+ Arrays.asList(
+ (PreferenceCategory)
+ settingsFragment.findPreference("notification_category"),
+ (PreferenceCategory) settingsFragment.findPreference("advanced"));
+ for (String key :
+ (runsTwentySix()
+ ? UNUSED_SETTINGS_POST_TWENTYSIX
+ : UNUSED_SETTINGS_PRE_TWENTYSIX)) {
Preference preference = settingsFragment.findPreference(key);
if (preference != null) {
for (PreferenceCategory category : categories) {
@@ -115,7 +127,8 @@ public class Compatibility {
}
if (Compatibility.runsTwentySix()) {
if (targetsTwentySix(settingsFragment.getContext())) {
- Preference preference = settingsFragment.findPreference(SettingsActivity.KEEP_FOREGROUND_SERVICE);
+ Preference preference =
+ settingsFragment.findPreference(SettingsActivity.KEEP_FOREGROUND_SERVICE);
if (preference != null) {
for (PreferenceCategory category : categories) {
if (category != null) {
@@ -136,11 +149,12 @@ public class Compatibility {
context.startService(intent);
}
} catch (RuntimeException e) {
- Log.d(Config.LOGTAG, context.getClass().getSimpleName() + " was unable to start service");
+ Log.d(
+ Config.LOGTAG,
+ context.getClass().getSimpleName() + " was unable to start service");
}
}
-
@SuppressLint("UnsupportedChromeOsCameraSystemFeature")
public static boolean hasFeatureCamera(final Context context) {
final PackageManager packageManager = context.getPackageManager();
diff --git a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java
index 3e5ecec60395bb87d741ff4ff0271c7836bbbd2f..bcaa9cb4b06a4b3beaf87dc344f58537af726f94 100644
--- a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java
+++ b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java
@@ -22,12 +22,14 @@ import android.provider.OpenableColumns;
import android.util.Log;
import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.Properties;
@@ -40,6 +42,13 @@ import eu.siacs.conversations.services.ExportBackupService;
* Used to implement java.net.URLConnection and android.webkit.MimeTypeMap.
*/
public final class MimeUtils {
+
+ public static final List AMBIGUOUS_CONTAINER_FORMATS = ImmutableList.of(
+ "application/ogg",
+ "video/3gpp", // .3gp files can contain audio, video or both
+ "video/3gpp2"
+ );
+
private static final Map mimeTypeToExtensionMap = new HashMap<>();
private static final Map extensionToMimeTypeMap = new HashMap<>();
@@ -229,36 +238,8 @@ public final class MimeUtils {
add("video/3gpp", "3gp");
add("video/3gpp2", "3gpp2");
add("video/3gpp2", "3g2");
- add("video/avi", "avi");
- add("video/dl", "dl");
- add("video/dv", "dif");
- add("video/dv", "dv");
- add("video/fli", "fli");
- add("video/m4v", "m4v");
- add("video/mp2ts", "ts");
- add("video/ogg", "ogv");
- add("video/mpeg", "mpeg");
- add("video/mpeg", "mpg");
- add("video/mpeg", "mpe");
- add("video/mp4", "mp4");
- add("video/mpeg", "VOB");
- add("video/quicktime", "qt");
- add("video/quicktime", "mov");
- add("video/vnd.mpegurl", "mxu");
- add("video/webm", "webm");
- add("video/x-la-asf", "lsf");
- add("video/x-la-asf", "lsx");
- add("video/x-matroska", "mkv");
- add("video/x-mng", "mng");
- add("video/x-ms-asf", "asf");
- add("video/x-ms-asf", "asx");
- add("video/x-ms-wm", "wm");
- add("video/x-ms-wmv", "wmv");
- add("video/x-ms-wmx", "wmx");
- add("video/x-ms-wvx", "wvx");
- add("video/x-sgi-movie", "movie");
- add("video/x-webex", "wrf");
add("audio/3gpp", "3gpp");
+ add("audio/3gpp", "3gp");
add("audio/aac", "aac");
add("audio/aac-adts", "aac");
add("audio/amr", "amr");
@@ -398,6 +379,35 @@ public final class MimeUtils {
add("text/x-tex", "cls");
add("text/x-vcalendar", "vcs");
add("text/x-vcard", "vcf");
+ add("video/avi", "avi");
+ add("video/dl", "dl");
+ add("video/dv", "dif");
+ add("video/dv", "dv");
+ add("video/fli", "fli");
+ add("video/m4v", "m4v");
+ add("video/mp2ts", "ts");
+ add("video/ogg", "ogv");
+ add("video/mpeg", "mpeg");
+ add("video/mpeg", "mpg");
+ add("video/mpeg", "mpe");
+ add("video/mp4", "mp4");
+ add("video/mpeg", "VOB");
+ add("video/quicktime", "qt");
+ add("video/quicktime", "mov");
+ add("video/vnd.mpegurl", "mxu");
+ add("video/webm", "webm");
+ add("video/x-la-asf", "lsf");
+ add("video/x-la-asf", "lsx");
+ add("video/x-matroska", "mkv");
+ add("video/x-mng", "mng");
+ add("video/x-ms-asf", "asf");
+ add("video/x-ms-asf", "asx");
+ add("video/x-ms-wm", "wm");
+ add("video/x-ms-wmv", "wmv");
+ add("video/x-ms-wmx", "wmx");
+ add("video/x-ms-wvx", "wvx");
+ add("video/x-sgi-movie", "movie");
+ add("video/x-webex", "wrf");
add("x-conference/x-cooltalk", "ice");
add("x-epoc/x-sisx-app", "sisx");
applyOverrides();
diff --git a/src/main/java/eu/siacs/conversations/utils/PermissionUtils.java b/src/main/java/eu/siacs/conversations/utils/PermissionUtils.java
index 80b58d8cbef886a28e04a3489ded4cccdf4e2479..004676156f2ffb8d0c98a2256a015f32287a6cbc 100644
--- a/src/main/java/eu/siacs/conversations/utils/PermissionUtils.java
+++ b/src/main/java/eu/siacs/conversations/utils/PermissionUtils.java
@@ -8,7 +8,9 @@ import android.os.Build;
import androidx.core.app.ActivityCompat;
import com.google.common.collect.ImmutableList;
+import com.google.common.primitives.Ints;
+import java.util.ArrayList;
import java.util.List;
public class PermissionUtils {
@@ -40,11 +42,41 @@ public class PermissionUtils {
return null;
}
- public static boolean hasPermission(final Activity activity, final List permissions, final int requestCode) {
+ public static class PermissionResult {
+ public final String[] permissions;
+ public final int[] grantResults;
+
+ public PermissionResult(String[] permissions, int[] grantResults) {
+ this.permissions = permissions;
+ this.grantResults = grantResults;
+ }
+ }
+
+ public static PermissionResult removeBluetoothConnect(
+ final String[] inPermissions, final int[] inGrantResults) {
+ final List outPermissions = new ArrayList<>();
+ final List outGrantResults = new ArrayList<>();
+ for (int i = 0; i < Math.min(inPermissions.length, inGrantResults.length); ++i) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
+ if (inPermissions[i].equals(Manifest.permission.BLUETOOTH_CONNECT)) {
+ continue;
+ }
+ }
+ outPermissions.add(inPermissions[i]);
+ outGrantResults.add(inGrantResults[i]);
+ }
+
+ return new PermissionResult(
+ outPermissions.toArray(new String[0]), Ints.toArray(outGrantResults));
+ }
+
+ public static boolean hasPermission(
+ final Activity activity, final List permissions, final int requestCode) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
final ImmutableList.Builder missingPermissions = new ImmutableList.Builder<>();
for (final String permission : permissions) {
- if (ActivityCompat.checkSelfPermission(activity, permission) != PackageManager.PERMISSION_GRANTED) {
+ if (ActivityCompat.checkSelfPermission(activity, permission)
+ != PackageManager.PERMISSION_GRANTED) {
missingPermissions.add(permission);
}
}
@@ -52,7 +84,8 @@ public class PermissionUtils {
if (missing.size() == 0) {
return true;
}
- ActivityCompat.requestPermissions(activity, missing.toArray(new String[0]), requestCode);
+ ActivityCompat.requestPermissions(
+ activity, missing.toArray(new String[0]), requestCode);
return false;
} else {
return true;
diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java
index f7befe66f2ab4284e7416820bf3e0d376147c7e9..b3d19a28989edd8f7509313f29680754eeb547a1 100644
--- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java
@@ -477,8 +477,10 @@ public class UIHelper {
public static String getFileDescriptionString(final Context context, final Message message) {
final String mime = message.getMimeType();
- if (mime == null) {
+ if (Strings.isNullOrEmpty(mime)) {
return context.getString(R.string.file);
+ } else if (MimeUtils.AMBIGUOUS_CONTAINER_FORMATS.contains(mime)) {
+ return context.getString(R.string.multimedia_file);
} else if (mime.startsWith("audio/")) {
return context.getString(R.string.audio);
} else if (mime.startsWith("video/")) {
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java
index 9f091718f2f65e241a502b5148d596507d989501..034fe71ed019f47058367c4f3d715eae660c856d 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java
@@ -892,7 +892,15 @@ public class JingleConnectionManager extends AbstractConnectionManager {
for (final AbstractJingleConnection connection : this.connections.values()) {
if (connection.getId().sessionId.equals(sessionId)) {
if (connection instanceof JingleRtpConnection) {
- ((JingleRtpConnection) connection).rejectCall();
+ try {
+ ((JingleRtpConnection) connection).rejectCall();
+ return;
+ } catch (final IllegalStateException e) {
+ Log.w(
+ Config.LOGTAG,
+ "race condition on rejecting call from notification",
+ e);
+ }
}
}
}
@@ -908,12 +916,12 @@ public class JingleConnectionManager extends AbstractConnectionManager {
}
}
- public void failProceed(Account account, final Jid with, String sessionId) {
+ public void failProceed(Account account, final Jid with, final String sessionId, final String message) {
final AbstractJingleConnection.Id id =
AbstractJingleConnection.Id.of(account, with, sessionId);
final AbstractJingleConnection existingJingleConnection = connections.get(id);
if (existingJingleConnection instanceof JingleRtpConnection) {
- ((JingleRtpConnection) existingJingleConnection).deliverFailedProceed();
+ ((JingleRtpConnection) existingJingleConnection).deliverFailedProceed(message);
}
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java
index c93e323e5aa24ba5c89c89c9e1afa7a12c242319..0704da0a3363dc9b8b3f88db164c40be43cdd225 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java
@@ -802,7 +802,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
} catch (final WebRTCWrapper.InitializationException e) {
Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to initialize WebRTC");
webRTCWrapper.close();
- sendSessionTerminate(Reason.FAILED_APPLICATION);
+ sendSessionTerminate(Reason.FAILED_APPLICATION, e.getMessage());
return;
}
final org.webrtc.SessionDescription sdp =
@@ -933,10 +933,10 @@ public class JingleRtpConnection extends AbstractJingleConnection
}
}
- void deliverFailedProceed() {
+ void deliverFailedProceed(final String message) {
Log.d(
Config.LOGTAG,
- id.account.getJid().asBareJid() + ": receive message error for proceed message");
+ id.account.getJid().asBareJid() + ": receive message error for proceed message ("+Strings.nullToEmpty(message)+")");
if (transition(State.TERMINATED_CONNECTIVITY_ERROR)) {
webRTCWrapper.close();
Log.d(
@@ -1277,7 +1277,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
webRTCWrapper.close();
final Reason reason = Reason.ofThrowable(throwable);
if (isInState(targetState)) {
- sendSessionTerminate(reason);
+ sendSessionTerminate(reason, throwable.getMessage());
} else {
sendRetract(reason);
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java
index e95a7e36d11a6987a8bd51f50a34ba4021ffbc81..e7693d6a8345bcb01bd6354caa25a5a529a1c9e5 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpContentMap.java
@@ -182,6 +182,9 @@ public class RtpContentMap {
final IceUdpTransportInfo.Credentials credentials =
Iterables.getFirst(allCredentials, null);
if (allCredentials.size() == 1 && credentials != null) {
+ if (Strings.isNullOrEmpty(credentials.password) || Strings.isNullOrEmpty(credentials.ufrag)) {
+ throw new IllegalStateException("Credentials are missing password or ufrag");
+ }
return credentials;
}
throw new IllegalStateException("Content map does not have distinct credentials");
diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml
index 60b08dac1aeeb015d36b5b7db557138ce773246b..de6b957c0a8e6d745b547eadc21bb6738770b5af 100644
--- a/src/main/res/values-de/strings.xml
+++ b/src/main/res/values-de/strings.xml
@@ -417,6 +417,7 @@
Video
Bild
Vektorgrafik
+ Multimediadatei
PDF-Dokument
Android App
Kontakt
diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml
index a0f7cf4281ad6048fbc5c73ee346b35b359b681b..99593d81efa05c5c36a00651c55365d36c3c0c74 100644
--- a/src/main/res/values-gl/strings.xml
+++ b/src/main/res/values-gl/strings.xml
@@ -417,6 +417,7 @@
video
imaxe
gráfico de vector
+ ficheiro multimedia
documento PDF
App Android
Contacto
diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml
index 93a4e8c464996f80333fdc38edebdbf749b27777..5fd38b7fd16832963b2bdf035a0f99cfd1781a3e 100644
--- a/src/main/res/values-ja/strings.xml
+++ b/src/main/res/values-ja/strings.xml
@@ -957,4 +957,6 @@
プレーンテキスト文書
アカウント登録はサポートされていません
XMPPアドレスがみつかりません
-
+ 一時的な認証失敗
+
+
diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml
index 8de69bbe8fe7a7c98bd006d7461c9168004e1e82..8d1201e090cfc2ed865cc5cf7606ecdd172cbdb9 100644
--- a/src/main/res/values-pl/strings.xml
+++ b/src/main/res/values-pl/strings.xml
@@ -423,6 +423,7 @@
plik wideo
obraz
grafika wektorowa
+ plik multimediów
Dokument PDF
Aplikacja Androida
Kontakt
@@ -1003,4 +1004,6 @@ Administrator twojego serwera będzie mógł czytać twoje wiadomości, ale moż
Dokument zwykłego tekstu
Rejestracja kont nie jest wspierana
Nie znaleziono adresu XMPP
-
+ Tymczasowy błąd uwierzytelniania
+
+
diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml
index 9e63c3460ee14a1ccde8bfda12d04fc1548b178b..8b4add4a41250da6605b33c94e1a60685447c3b1 100644
--- a/src/main/res/values-pt-rBR/strings.xml
+++ b/src/main/res/values-pt-rBR/strings.xml
@@ -420,6 +420,7 @@
vídeo
imagem
gráfico vetorial
+ arquivo multimídia
Documento PDF
Aplicativo Android
Contato
diff --git a/src/main/res/values-ro-rRO/strings.xml b/src/main/res/values-ro-rRO/strings.xml
index 8448e5a801ca3859d02fbde6d4bd270cd60c591d..2d9749e0d3aa0ad089c64af478ed3a32559dafac 100644
--- a/src/main/res/values-ro-rRO/strings.xml
+++ b/src/main/res/values-ro-rRO/strings.xml
@@ -420,6 +420,7 @@
video
imagine
grafic vectorial
+ fișier multimedia
document PDF
Aplicație Android
Contact
diff --git a/src/main/res/values-sv/strings.xml b/src/main/res/values-sv/strings.xml
index 0cda9fb71e14c3df9aa061d227b01a7c7ac2802a..652544ccb4e9a4f71e14afbf934deeb752271269 100644
--- a/src/main/res/values-sv/strings.xml
+++ b/src/main/res/values-sv/strings.xml
@@ -201,6 +201,7 @@
XEP-0191: Blocking Command
XEP-0237: Roster Versioning
XEP-0198: Stream Management
+ XEP-0215: External Service Discovery
XEP-0163: PEP (Avatarbilder / OMEMO)
XEP-0363: Ladda upp via HTTP
XEP-0357: Push
@@ -464,6 +465,7 @@
Nerladdning gick fel: Filen hittades inte
Nerladdningen gick fel: Kunder inte ansluta till server
Nerladdning gick fel: Kunde inte skriva fil
+ Nedladdning misslyckades: Ogiltig fil
Tor-nätverk ej tillgängligt
Bind-fel
Den här servern ansvarar inte för den här domänen
@@ -537,11 +539,15 @@
Säkerhetsfel: Ogiltig filåtkomst!
Ingen applikation hittades för att dela URI
Dela URI med...
+
Du registrerar dig med ditt telefonnummer och Quicksy kommer automatiskt – baserat på telefonnumren i din adressbok – att föreslå möjliga kontakter till dig.
Genom att registrera dig godkänner du vår integritetspolicy.]]>
Acceptera och gå vidare
+ En guide har skapats för kontoskapande på conversations.im.¹\nNär du väljer conversations.im som leverantör kommer du att kunna kommunicera med användare av andra leverantörer genom att ge dem din fullständiga XMPP-adress.
Din fullständiga XMPP-adress kommer att vara: %s
Skapa konto
Använd min egen leverantör
Välj användarnamn
+ Hantera tillgänglighet manuellt
+ Ställ in din tillgänglighet när du redigerar ditt statusmeddelande.
Statusmeddelande
Tillgänglig
Online
@@ -559,6 +565,8 @@
Kort
Medium
Lång
+ Gör användandet offentligt
+ Låter dina kontakter veta när du använder Conversations
Privatliv
Tema
Välj färgschema
@@ -591,6 +599,8 @@
Visa felmeddelande
Felmeddelande
Databesparing
+ Ditt operativsystem begränsar åtkomsten till Internet i bakgrunden för %1$s. För att få aviseringar om nya meddelanden bör du tillåta obegränsad åtkomst för %1$s, när databesparing är på.\n %1$s kommer fortfarande att anstränga sig för att spara data när det är möjligt.
+ Din enhet stöder inte inaktivering av databesparing för %1$s.
Det gick inte att skapa en tillfällig fil
Denna enhet har verifierats
Kopiera fingeravtryck
@@ -613,10 +623,13 @@
Rensa privat lagring där filer lagras (De kan om-laddas från servern)
Jag följde denna länk från en trovärdig källa
Du håller på att verifiera OMEMO-nyckeln för %1$s efter att du följt en länk. Detta är endast säkert om du följde länken från en trovärdig källa där endast %2$s kan ha publiserat denna länk.
+ Du är på väg att verifiera OMEMO-nycklarna för ditt eget konto. Detta är bara säkert om du följde den här länken från en pålitlig källa där bara du kunde ha publicerat den här länken.
+ Fortsätt
Verifiera OMEMO-nycklar
Visa inaktiva
Dölj inaktiva
Lita ej på enhet
+ Är du säker på att du vill ta bort verifieringen av den här enheten?\nDen här enheten och meddelanden från den kommer att markeras som \"Ej betrodd\".
- %d sekund
- %d sekunder
@@ -649,12 +662,15 @@
Korresponderande konversationer är stängda.
Kontakt blockerad.
Notifieringar från främlingar
+ Meddela för meddelanden och samtal från främlingar.
Mottagna meddelanden från främlingar
Blockera främling
Blockera hel domän
online just nu
Försök dekryptera igen
Sessionsfel
+ Nedgraderad SASL-mekanism
+ Servern kräver registrering via webbplatsen
Öppna webbsida
Ingen applikation hittades för att kunna öppna webbsidan
Se upp-notifikationer
@@ -662,46 +678,110 @@
Idag
Igår
Bekräfta värdnamn med DNSSEC
+ Servercertifikat som innehåller det validerade värdnamnet anses vara verifierade
Certifikatet innehåller ej en XMPP-adress
delvis
Spela in video
Kopiera till urklipp
Meddelande kopierat till urklipp
Meddelande
+ Privata meddelanden är inaktiverade
+ Skyddade applikationer
+ För att fortsätta ta emot aviseringar, även när skärmen är avstängd, måste du lägga till Conversations i listan över skyddade applikationer.
Godkänn okänt certifikat?
Servercertifikatet är inte signerat av en känd certifikatutfärdare.
+ Acceptera servernamn som inte matchar?
+ Servern kunde inte autentisera som \"%s\". Certifikatet är endast giltigt för:
Vill du ansluta ändå?
Certifikatdetaljer:
+ En gång
+ QR-läsaren behöver åtkomst till kameran
+ Bläddra till botten
+ Bläddra ner efter att du har skickat ett meddelande
+ Redigera Statusmeddelande
+ Redigera statusmeddelande
+ Inaktivera kryptering
+ %1$s kan inte skicka krypterade meddelanden till %2$s. Detta kan bero på att din kontakt använder en föråldrad server eller klient som inte kan hantera OMEMO.
+ Det gick inte att hämta enhetslistan
+ Det gick inte att hämta krypteringsnycklar
+ Tips: I vissa fall kan detta åtgärdas genom att lägga till varandra i era respektive kontaktlistor.
+ Är du säker på att du vill inaktivera OMEMO-kryptering för den här konversationen?\nDetta gör att din serveradministratör kan läsa dina meddelanden, men det kan också vara det enda sättet att kommunicera med människor som använder äldre klienter.
+ Inaktivera nu
Utkast:
+ OMEMO-kryptering
+ OMEMO kommer alltid att användas för privata konversationer och privata gruppchattar.
+ OMEMO kommer att användas som standard för nya konversationer.
+ OMEMO måste manuellt aktiveras för varje ny konversation.
Skapa genväg
+ Textstorlek
+ Den relativa teckenstorleken som används i appen.
+ På som standard
+ Av som standard
Liten
Mellan
Stor
+ Meddelandet är inte krypterat för den här enheten.
+ Misslyckades med att dekryptera OMEMO-meddelandet.
+ ångra
+ Platsdelning är inaktiverat
+ Lås position
+ Lås upp position
Kopiera plats
Dela plats
+ Hänvisningar
Dela plats
Visa plats
Dela
+ Det gick inte att starta inspelningen
Var god dröj...
+ Ge %1$s tillgång till mikrofonen
Söka i meddelanden
GIF
+ Visa konversation
+ Dela plats-tillägget
+ Kopiera webbadress
Kopiera XMPP-adress
+ HTTP-fildelning för S3
+ Direktsök
+ Gruppkonversationens visningsbild
+ Värden stöder inte visningsbilder för gruppkonversationer
+ Endast ägaren kan ändra visningsbilden för gruppkonversationen
+ Kontaktnamn
Smeknamn
Namn
Att ange ett namn är valfritt
Gruppchattens namn
+ Kunde inte att spara inspelningen
+ Förgrundsservice
+ Statusinformation
+ Anslutningsproblem
+ Meddelanden
+ Samtal
+ Meddelanden
+ Inkommande samtal
+ Pågående samtal
+ Tysta meddelanden
+ Misslyckade leveranser
+ Videokompression
+ Visa media
Deltagare
+ Mediautforskare
+ Videokvalitet
Mellan (360p)
Hög (720p)
+ avbruten
+ Du håller redan på att skriva ett meddelande.
Välj ett land
telefonnummer
Bekräfta ditt telefonnummer
+ tillbaka
Ja
Nej
Bekräftar...
Okänt nätverksfel.
För många försök
Du använder en föråldrad version av denna app.
+ Uppdatera
Ditt namn
Skriv in ditt namn
Avslå begäran
@@ -709,38 +789,130 @@
Starta Orbot
e-bok
Öppna med...
+ Konversationens profilbild
Välj konto
Återställa säkerhetskopiering
Återställa
Ange ditt lösenord till kontot %s för att återställa säkerhetskopian.
Det gick inte att återställa säkerhetskopian.
+ Säkerhetskopia & Återställ
+ Ange XMPP-adress
Skapa gruppchatt
+ Anslut till publik gruppkonversation
Skapa sluten gruppchatt
+ Skapa publik gruppkonversation
Kanalnamn
XMPP-adress
Vänligen ange ett namn på kanalen
Ange en XMPP-adress
Detta är en XMPP-adress. Ange ett namn.
+ Skapar publik gruppkonversation...
Denna kanal finns redan
Du har gått med i en befintlig kanal
+ Det gick inte att spara kanalkonfigurationen
+ Tillåt vem som helst att ändra ämnet
+ Tillåt vem som helst att bjuda in andra
+ Vem som helst kan ändra ämnet.
+ Ägaren kan ändra ämnet.
+ Administratörer kan ändra ämnet.
+ Ägare kan bjuda in andra.
+ Vem som helst kan bjuda in andra.
XMPP-adresser är synliga för administratörer.
XMPP-adresser är synliga för alla.
+ Den här publika gruppkonversationen har inga deltagare. Bjud in dina kontakter eller använd \'dela-knappen\' för att dela XMPP-adressen.
Denna slutna gruppchatt har inga deltagare.
Hantera rättigheter
+ Sök efter deltagare
För stor fil
Bifoga
Upptäck kanaler
+ Sök efter gruppkonversationer
+ Möjlig integritetskränkning!
Jag har redan ett konto
Lägg till befintligt konto
Skapa nytt konto
Detta verkar vara ett domännamn
Lägg till ändå
Detta ser ut som en kanaladress
+ Dela säkerhetskopior
+ Säkerhetskopior för Conversations
+ Händelse
+ Öppna säkerhetskopia
Filen du valde är inte en säkerhetskopia till Conversations
+ Det här kontot har redan konfigurerats
+ Var god ange lösenordet för det här kontot
+ Det gick inte att utföra den här åtgärden
+ Anslut till publik gruppkonversation...
+ Delnings-appen gav inte behörighet till att komma åt den här filen.
+
+ jabber.network
+ Lokal server
+ De flesta användare bör välja \"jabber.network\" för bättre förslag från hela det offentliga XMPP-ekosystemet.
+ Metod för kanalupptäckt
+ Säkerhetskopiering
Om
Aktivera ett konto
+ Ring
+ Inkommande samtal
+ Inkommande videosamtal
+ Ansluter
+ Ansluten
+ Återansluter
+ Accepterar samtal
+ Avslutar samtal
+ Svara
+ Avvisa
+ Upptäcker enheter
+ Ringer
Upptagen
+ Kunde inte koppla samtal
+ Anslutning bröts
+ Återkallat samtal
+ Appmisslyckande
+ Verifikationsproblem
+ Lägg på
+ Pågående samtal
+ Pågående videosamtal
+ Återansluter samtalet
+ Återansluter videosamtalet
+ Inaktivera Tor för att ringa samtal
+ Inkommande samtal
+ Inkommande samtal · %s
+ Missat samtal · %s
+ Utgående samtal
+ Pågående samtal · %s
+ Missat samtal
+ Röstsamtal
+ Videosamtal
+ Hjälp
+ Växla till konversation
+ Din mikrofon är inte tillgänglig
+ Du kan bara ha ett samtal åt gången.
+ Återgå till pågående samtal
+ Kunde inte växla kamera
Fäst flik till toppen
Ta bort flik från toppen
+ GPX-spår
+ Kunde inte korrigera meddelandet
+ Alla konversationer
+ Den här konversationen
+ Din visningsbild
+ Visningsbild för %s
+ Krypterad med OMEMO
+ Krypterad med OpenPGP
+ Inte krypterad
+ Avsluta
+ Spela in ett röstmeddelande
+ Spela upp ljud
+ Pausa ljud
+ Lägg till kontakt, skapa eller gå med i gruppchatt eller upptäck kanaler
+
+ - Visa %1$d deltagare
+ - Visa %1$d deltagare
+
+ Misslyckade leveranser
Fler alternativ
+ Ingen applikation hittades
+ Bjud in till Conversations
+ Ingen XMPP-adress hittades
diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml
index 87aec571ee29f00d24ff25fb51f9e240d9ef2807..03145ba512be127ffd3d28db8879ac3b00fcad38 100644
--- a/src/main/res/values-zh-rCN/strings.xml
+++ b/src/main/res/values-zh-rCN/strings.xml
@@ -414,6 +414,7 @@
视频
图片
矢量图
+ 多媒体文件
PDF文档
Android App
联系人
diff --git a/src/main/res/values-zh-rTW/strings.xml b/src/main/res/values-zh-rTW/strings.xml
index ca4b696fcaff761da1cd811c083c4f582a67225f..56102faaec37410f69e5850f24e4abd242992b8e 100644
--- a/src/main/res/values-zh-rTW/strings.xml
+++ b/src/main/res/values-zh-rTW/strings.xml
@@ -1,97 +1,132 @@
設定
- 新對話
+ 新會話
管理帳戶
- 聯絡人詳情
+ 管理帳戶
+ 關閉會話
+ 聯絡人詳細資料
+ 群組聊天詳細資料
+ 頻道詳細資料
新增帳戶
- 編輯姓名
- 添加到地址薄
- 從列表中刪除
+ 編輯名稱
+ 新增至通訊錄
+ 從名冊中刪除
封鎖連絡人
解除封鎖連絡人
封鎖網域
解除封鎖網域
+ 封鎖成員
+ 解除封鎖成員
管理帳戶
- 設置
- 分享到 Conversation
+ 設定
+ 分享至 Conversation
開始會話
+ 選擇聯絡人
+ 選擇聯絡人
+ 透過帳戶分享
封鎖清單
剛剛
1 分鐘前
- %d分鐘前
- 正在發送…
- 訊息解密中,請稍候…
- OpenPGP 加密的信息
- 該名稱已存在
+ %d 分鐘前
+
+ - %d 則未讀會話
+
+
+ 正在傳送…
+ 正在解密訊息,請稍候…
+ OpenPGP 已加密的訊息
+ 暱稱已有人使用
+ 無效的暱稱
管理員
- 所有者
+ 擁有者
版主
- 參與者
+ 成員
訪客
- 要封鎖 %s 讓它不能送訊息給你嗎?
- 要解除封鎖 %s 讓它可以送訊息給你嗎?
- 要封鎖來自 %s 的所有連絡人嗎?
- 要解除封鎖來自 %s 的所有連絡人嗎?
+ 要將 %s 從你的聯絡人清單中移除嗎?與此聯絡人的會話將不會被移除。
+ 要封鎖 %s 向您傳送訊息嗎?
+ 要解除封鎖 %s 並允許他們向您傳送訊息嗎?
+ 要封鎖來自 %s 的所有聯絡人嗎?
+ 要解除封鎖來自 %s 的所有聯絡人嗎?
連絡人已封鎖
+ 已封鎖
+ 要從書籤中移除 %s 嗎?與此書籤相關的會話將不會被移除。
在伺服器上註冊新帳戶
- 在伺服器上改變密碼
- 分享…
- 連絡人
- 連絡人
+ 在伺服器上變更密碼
+ 分享至…
+ 開始會話
+ 邀請聯絡人
+ 邀請
+ 聯絡人
+ 聯絡人
取消
- 設置
- 添加
+ 設定
+ 新增
編輯
刪除
封鎖
解除封鎖
- 保存
+ 儲存
完成
- 現在發送
+ %1$s 已當機
+ 立即傳送
不再詢問
+ 無法連線至帳戶
+ 無法連線至多個帳戶
+ 輕觸以管理你的帳戶
附加檔案
- 添加連絡人
+ 要將這位遺失的聯絡人新增至你的聯絡人清單嗎?
+ 新增聯絡人
傳遞失敗
- 正在分享檔案中,請稍候…
+ 正在準備傳送圖片
+ 正在準備傳送圖片
+ 正在分享檔案,請稍候…
清除歷史記錄
清除會話記錄
- 選擇設備
- 發送未加密的訊息
- 送訊息
- 送訊息給 %s
- 送 OMEMO 加密訊息
- 送 v\\OMEMO 加密訊息
- 送 OpenPGP 加密訊息
- 不加密發送
+ 刪除檔案
+ 之後關閉此會話
+ 選擇裝置
+ 傳送未加密的訊息
+ 傳送訊息
+ 傳送訊息至 %s
+ 傳送 OMEMO 加密訊息
+ 傳送 v\\OMEMO 加密訊息
+ 傳送 OpenPGP 加密訊息
+ 新暱稱已被使用
+ 不加密傳送
解密失敗,可能是私密金鑰不正確。
OpenKeychain
- 重啟
+ 重新啟動
安裝
請安裝 OpenKeychain 以解密
- 輸入…
- 等待…
- 未發現 OpenPGP 金鑰
+ 正在提供…
+ 正在等候…
+ 找不到 OpenPGP 金鑰
未找到 OpenPGP 金鑰
- 常規
- 接收檔案
- 自動接收小於 … 的檔案
+ 一般
+ 接受檔案
+ 自動接受小於此大小的檔案
附件
通知
震動
收到新訊息時震動
- LED 燈通知
+ LED 通知
收到新訊息時閃爍通知燈
鈴聲
+ 通知音效
+ 收到新訊息時發出通知音效
+ 來電時響鈴
靜默期限
- 高級
- 總不發送崩潰報告
+ 進階
+ 永不傳送當機報告
確認訊息
- 讓聯絡人知道它們的訊息已經收到以及讀取
+ 讓你的聯絡人知道你已經收到並閱讀了他們的訊息
+ 防止截圖
+ 在多工畫面隱藏應用程式聯絡人並且封鎖螢幕截圖
UI
接受
產生了一個錯誤
- 你的帳號
+ 你的帳戶
發送線上連絡人列表更新
接收線上連絡人列表更新
請求線上連絡人列表更新
@@ -114,12 +149,12 @@
註冊完成
違反政策
伺服器不相容
- 流錯誤
+ 串流錯誤
TLS
OTR
OpenPGP
OMEMO
- 刪除帳號
+ 刪除帳戶
暫時不可用
發佈頭像
發佈 OpenPGP 公開金鑰
@@ -128,16 +163,21 @@
啟用帳戶
確定?
錄音
+ XMPP 位址
+ 封鎖 XMPP 位址
username@example.com
密碼
- 是否添加 %s 到地址薄?
+ 這不是有效的 XMPP 位址
+ 記憶體不足,圖片過大
+ 要將 %s 新增至通訊錄嗎?
伺服器資訊
XEP-0313: MAM
XEP-0280: 訊息複本
XEP-0352: 用戶端狀態指示
- XEP-0191: 封鎖指令
- XEP-0237: 花名冊版本控制
- XEP-0198: 流管理
+ XEP-0191: 封鎖命令
+ XEP-0237: 名冊版本設定
+ XEP-0198: 串流管理
+ XEP-0215: 外部服務探索
XEP-0163: PEP (替身 / OMEMO)
XEP-0363: HTTP 檔案上傳
XEP-0357: Push
@@ -151,25 +191,31 @@
OpenPGP 金鑰 ID
OMEMO 指紋
v\\OMEMO 指紋
- 其他設備
+ 其他裝置
信任的 OMEMO 指紋
- 獲取金鑰中
+ 正在擷取金鑰…
完成
解密
- 查找
- 輸入連絡人
- 查看連絡人詳細資訊
- 封鎖連絡人
- 解除封鎖連絡人
- 創建
- 選擇
- 連絡人已存在
+ 書籤
+ 尋找
+ 輸入聯絡人
+ 刪除聯絡人
+ 檢視聯絡人詳細資料
+ 封鎖聯絡人
+ 解除封鎖聯絡人
+ 建立
+ 選取
+ 聯絡人已存在
加入
- 保存為書簽
- 刪除書簽
+ channel@conference.example.com/nick
+ channel@conference.example.com
+ 儲存為書籤
+ 刪除書籤
+ 主旨
+ 正在加入群組聊天…
離開
- 連絡人已添加你到連絡人列表
- 反向添加
+ 聯絡人已新增至你的聯絡人清單
+ 新增回
%s 已讀此句
發佈
正在發佈…
@@ -180,25 +226,29 @@
至 %s
送私密訊息給 %s
連接
- 該帳號已存在
+ 此帳戶已存在
下一步
- 忽略
+ 工作階段已建立
+ 跳過
關閉通知
打開通知
+ 群組聊天需要密碼
輸入密碼
- 現在發送請求
+ 立即要求
忽略
- 安全
+ 安全性
允許更正訊息
允許您的連絡人追回編輯他們的訊息
- 高級設置
+ 專家設定
請謹慎使用
+ 關於 %s
靜默時間段
開始時間
結束時間
啟用靜默時間段
在靜默時間段內通知將保持靜音
其他
+ 同步處理書籤
用帳戶 %s
正在 HTTP 伺服器中檢查 %s
你沒有連接。請稍後重試
@@ -208,7 +258,11 @@
引用
拷貝原始URL
再次發送
- 檔案位址(URL)
+ 檔案 URL
+ 已複製 URL 到剪貼簿
+ 已複製 XMPP 位址到剪貼簿
+ 已複製錯誤訊息到剪貼簿
+ 網頁地址
掃描二維條碼
顯示二維條碼
顯示封鎖清單
@@ -216,15 +270,26 @@
確認
再試一遍
防止作業系統中斷你的連接
- 選檔案
- 接收中 %1$s (已完成 %2$d%%)
+ 建立備份
+ 備份檔案將被儲存至 %s
+ 正在建立備份檔案
+ 你的備份已建立
+ 此備份檔案已被儲存至 %s
+ 正在還原備份
+ 你的備份已還原
+ 不要忘記啟用帳戶。
+ 選擇檔案
+ 正在接收 %1$s (已完成 %2$d%%)
下載 %s
刪除 %s
檔案
- 打開 %s
- 發送中 (已完成 %1$d%%)
+ 開啟 %s
+ 正在傳送 (已完成 %1$d%%)
+ 正在準備分享檔案
可以下載 %s
取消傳送
+ 無法分享檔案
+ 檔案已刪除
在連絡人下方顯示唯讀標籤
啟用通知
帳戶頭像
@@ -418,20 +483,122 @@
再試解密ㄧ次
通訊對話錯誤
頭條通知
- 消息已經拷貝到剪貼板
+ 今天
+ 昨天
+ 錄製影片
+ 複製到剪貼簿
+ 訊息已複製到剪貼簿
+ 訊息
+ 私密訊息已停用
+ 受保護的應用程式
+ 接受未知憑證?
+ 憑證詳細資料:
+ 僅一次
+ 捲動至底部
+ 傳送訊息後向下捲動
+ 編輯狀態訊息
+ 編輯訊息
+ 停用加密
+ 無法擷取裝置清單
+ 無法擷取加密金鑰
+ 立即停用
OMEMO 加密
一對一以及私人群組的聊天一定會用 OMEMO
新的對話預設會用 OMEMO 加密
- 新的對話必須要手動開啟 OMEMO 加密
+ 新的會話必須要手動開啟 OMEMO 加密
+ 建立捷徑
字型大小
- App 中所使用的相對字型大小
+ 應用程式中使用的相對字型大小
預設開啟
預設關閉
小
- 適中
+ 中
大
+ 復原
+ 位置分享已停用
+ 固定位置
+ 取消固定位置
+ 複製位置
+ 分享位置
+ 方向
+ 分享位置
顯示位置
+ 分享
+ 無法開始錄製
+ 請稍候…
+ 授予 %1$s 以存取麥克風
搜尋訊息
+ GIF
+ 檢視會話
+ 分享位置外掛程式
+ 使用分享位置外掛程式而非內建地圖
+ 複製網站位址
+ 複製 XMPP 位址
+ 直接搜尋
+ 暱稱
+ 名稱
聊天群組名稱
+ 無法儲存錄製
+ 狀態資訊
+ 訊息
+ 通話
+ 訊息
+ 來電
+ 正在進行的通話
+ 無聲訊息
+ 訊息通知設定
+ 來電通知設定
+ 影片壓縮
+ 檢視媒體
+ 成員
+ 媒體瀏覽器
+ 影片質量
+ 低質量意味這更小的檔案
+ 中 (360P)
+ 高 (720P)
+ 已取消
+ 無效的國家碼
+ 選擇國家
+ 電話號碼
+ 驗證電話號碼
+ 請輸入您的電話號碼。
+ 搜尋國家
+ 驗證 %s
+ 重新傳送簡訊
+ 重新傳送簡訊 (%s)
+ 請等候 (%s)
+ 返回
+ 是
+ 否
+ 正在驗證…
+ 正在要求簡訊…
+ 未知網路錯誤。
+ 沒有網路連線。
+ 更新
+ 你的名稱
+ 輸入你的名稱
+ 拒絕要求
+ 電子書
+ 開啟為…
+ 選擇帳戶
+ 還原備份
+ 還原
+ 備份與還原
+ 建立群組聊天
+ 加入公用頻道
+ 建立私人群組聊天
+ 建立公用頻道
+ 頻道名稱
+ XMPP 位址
+ 活動
+ 開啟備份
+ 本機伺服器
+ 關於
忙碌
+ 說明
+ 釘選
+ 取消釘選
+ 離開
+ 播放音訊
+ 更多選項
diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml
index 9344a574472408a1e47a9114eb96de782ad7be61..b15a2a9d42719dda453632fb9d5b567e51f2d71d 100644
--- a/src/main/res/values/strings.xml
+++ b/src/main/res/values/strings.xml
@@ -416,6 +416,7 @@
video
image
vector graphic
+ multimedia file
PDF document
Android App
Contact
diff --git a/src/quicksy/res/values-zh-rTW/strings.xml b/src/quicksy/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000000000000000000000000000000000000..d4b37b79204765bbf648c13f3883f5b3b42a6040
--- /dev/null
+++ b/src/quicksy/res/values-zh-rTW/strings.xml
@@ -0,0 +1,8 @@
+
+
+ Quicksy 設定檔圖片
+ Quicksy 在您的國家無法使用。
+ 無法驗證伺服器身分。
+ 未知安全性錯誤。
+ 連線伺服器逾時。
+