uris = new ArrayList<>();
- for (File file : files) {
- uris.add(FileBackend.getUriForFile(this, file));
- }
- intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris);
- 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,
- s()
- ? PendingIntent.FLAG_IMMUTABLE
- | PendingIntent.FLAG_UPDATE_CURRENT
- : PendingIntent.FLAG_UPDATE_CURRENT);
- }
-
- NotificationCompat.Builder mBuilder =
- new NotificationCompat.Builder(getBaseContext(), "backup");
- mBuilder.setContentTitle(getString(R.string.notification_backup_created_title))
- .setContentText(getString(R.string.notification_backup_created_subtitle, path))
- .setStyle(
- new NotificationCompat.BigTextStyle()
- .bigText(
- getString(
- R.string.notification_backup_created_subtitle,
- FileBackend.getBackupDirectory(this)
- .getAbsolutePath())))
- .setAutoCancel(true)
- .setContentIntent(openFolderIntent)
- .setSmallIcon(R.drawable.ic_archive_white_24dp);
-
- if (shareFilesIntent != null) {
- mBuilder.addAction(
- R.drawable.ic_share_white_24dp,
- getString(R.string.share_backup_files),
- shareFilesIntent);
- }
-
- notificationManager.notify(NOTIFICATION_ID, mBuilder.build());
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return null;
- }
-
- private static class Progress {
- private final NotificationCompat.Builder builder;
- private final int max;
- private final int count;
-
- private Progress(NotificationCompat.Builder builder, int max, int count) {
- this.builder = builder;
- this.max = max;
- this.count = count;
- }
-
- private Notification build(int percentage) {
- builder.setProgress(max * 100, count * 100 + percentage, false);
- return builder.build();
- }
- }
-}
diff --git a/src/main/java/eu/siacs/conversations/services/MemorizingTrustManager.java b/src/main/java/eu/siacs/conversations/services/MemorizingTrustManager.java
index 520348943..d05fa4ac3 100644
--- a/src/main/java/eu/siacs/conversations/services/MemorizingTrustManager.java
+++ b/src/main/java/eu/siacs/conversations/services/MemorizingTrustManager.java
@@ -33,6 +33,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
+import android.os.Build;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Base64;
@@ -43,9 +44,21 @@ import androidx.appcompat.app.AppCompatActivity;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
+import com.google.common.base.Preconditions;
import com.google.common.io.ByteStreams;
import com.google.common.io.CharStreams;
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.crypto.BundledTrustManager;
+import eu.siacs.conversations.crypto.CombiningTrustManager;
+import eu.siacs.conversations.crypto.TrustManagers;
+import eu.siacs.conversations.crypto.XmppDomainVerifier;
+import eu.siacs.conversations.entities.MTMDecision;
+import eu.siacs.conversations.http.HttpConnectionManager;
+import eu.siacs.conversations.persistance.FileBackend;
+import eu.siacs.conversations.ui.MemorizingActivity;
+
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@@ -78,39 +91,40 @@ import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.crypto.XmppDomainVerifier;
-import eu.siacs.conversations.entities.MTMDecision;
-import eu.siacs.conversations.http.HttpConnectionManager;
-import eu.siacs.conversations.persistance.FileBackend;
-import eu.siacs.conversations.ui.MemorizingActivity;
-
/**
- * A X509 trust manager implementation which asks the user about invalid
- * certificates and memorizes their decision.
- *
- * The certificate validity is checked using the system default X509
- * TrustManager, creating a query Dialog if the check fails.
- *
- * WARNING: This only works if a dedicated thread is used for
- * opening sockets!
+ * A X509 trust manager implementation which asks the user about invalid certificates and memorizes
+ * their decision.
+ *
+ *
The certificate validity is checked using the system default X509 TrustManager, creating a
+ * query Dialog if the check fails.
+ *
+ *
WARNING: This only works if a dedicated thread is used for opening sockets!
*/
public class MemorizingTrustManager {
- private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.US);
+ private static final SimpleDateFormat DATE_FORMAT =
+ new SimpleDateFormat("yyyy-MM-dd", Locale.US);
- final static String DECISION_INTENT = "de.duenndns.ssl.DECISION";
- public final static String DECISION_INTENT_ID = DECISION_INTENT + ".decisionId";
- public final static String DECISION_INTENT_CERT = DECISION_INTENT + ".cert";
- public final static String DECISION_TITLE_ID = DECISION_INTENT + ".titleId";
- final static String NO_TRUST_ANCHOR = "Trust anchor for certification path not found.";
- private static final Pattern PATTERN_IPV4 = Pattern.compile("\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
- private static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
- private static final Pattern PATTERN_IPV6_6HEX4DEC = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
- private static final Pattern PATTERN_IPV6_HEXCOMPRESSED = Pattern.compile("\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z");
- private static final Pattern PATTERN_IPV6 = Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z");
- private final static Logger LOGGER = Logger.getLogger(MemorizingTrustManager.class.getName());
+ static final String DECISION_INTENT = "de.duenndns.ssl.DECISION";
+ public static final String DECISION_INTENT_ID = DECISION_INTENT + ".decisionId";
+ public static final String DECISION_INTENT_CERT = DECISION_INTENT + ".cert";
+ public static final String DECISION_TITLE_ID = DECISION_INTENT + ".titleId";
+ static final String NO_TRUST_ANCHOR = "Trust anchor for certification path not found.";
+ private static final Pattern PATTERN_IPV4 =
+ Pattern.compile(
+ "\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
+ private static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED =
+ Pattern.compile(
+ "\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
+ private static final Pattern PATTERN_IPV6_6HEX4DEC =
+ Pattern.compile(
+ "\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
+ private static final Pattern PATTERN_IPV6_HEXCOMPRESSED =
+ Pattern.compile(
+ "\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z");
+ private static final Pattern PATTERN_IPV6 =
+ Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z");
+ private static final Logger LOGGER = Logger.getLogger(MemorizingTrustManager.class.getName());
static String KEYSTORE_DIR = "KeyStore";
static String KEYSTORE_FILE = "KeyStore.bks";
private static int decisionId = 0;
@@ -126,54 +140,65 @@ public class MemorizingTrustManager {
private String poshCacheDir;
/**
- * Creates an instance of the MemorizingTrustManager class that falls back to a custom TrustManager.
- *
- * You need to supply the application context. This has to be one of:
- * - Application
- * - Activity
- * - Service
- *
- * The context is used for file management, to display the dialog /
- * notification and for obtaining translated strings.
+ * Creates an instance of the MemorizingTrustManager class that falls back to a custom
+ * TrustManager.
*
- * @param m Context for the application.
- * @param defaultTrustManager Delegate trust management to this TM. If null, the user must accept every certificate.
+ *
You need to supply the application context. This has to be one of: - Application -
+ * Activity - Service
+ *
+ *
The context is used for file management, to display the dialog / notification and for
+ * obtaining translated strings.
+ *
+ * @param context Context for the application.
+ * @param defaultTrustManager Delegate trust management to this TM. If null, the user must
+ * accept every certificate.
*/
- public MemorizingTrustManager(Context m, X509TrustManager defaultTrustManager) {
- init(m);
+ public MemorizingTrustManager(
+ final Context context, final X509TrustManager defaultTrustManager) {
+ init(context);
this.appTrustManager = getTrustManager(appKeyStore);
this.defaultTrustManager = defaultTrustManager;
}
/**
* Creates an instance of the MemorizingTrustManager class using the system X509TrustManager.
- *
- * You need to supply the application context. This has to be one of:
- * - Application
- * - Activity
- * - Service
- *
- * The context is used for file management, to display the dialog /
- * notification and for obtaining translated strings.
*
- * @param m Context for the application.
+ *
You need to supply the application context. This has to be one of: - Application -
+ * Activity - Service
+ *
+ *
The context is used for file management, to display the dialog / notification and for
+ * obtaining translated strings.
+ *
+ * @param context Context for the application.
*/
- public MemorizingTrustManager(Context m) {
- init(m);
+ public MemorizingTrustManager(final Context context) {
+ init(context);
this.appTrustManager = getTrustManager(appKeyStore);
- this.defaultTrustManager = getTrustManager(null);
+ try {
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) {
+ this.defaultTrustManager = TrustManagers.defaultWithBundledLetsEncrypt(context);
+ } else {
+ this.defaultTrustManager = TrustManagers.createDefaultTrustManager();
+ }
+ } catch (final NoSuchAlgorithmException
+ | KeyStoreException
+ | CertificateException
+ | IOException e) {
+ throw new RuntimeException(e);
+ }
}
private static boolean isIp(final String server) {
- return server != null && (
- PATTERN_IPV4.matcher(server).matches()
+ return server != null
+ && (PATTERN_IPV4.matcher(server).matches()
|| PATTERN_IPV6.matcher(server).matches()
|| PATTERN_IPV6_6HEX4DEC.matcher(server).matches()
|| PATTERN_IPV6_HEX4DECCOMPRESSED.matcher(server).matches()
|| PATTERN_IPV6_HEXCOMPRESSED.matcher(server).matches());
}
- private static String getBase64Hash(X509Certificate certificate, String digest) throws CertificateEncodingException {
+ private static String getBase64Hash(X509Certificate certificate, String digest)
+ throws CertificateEncodingException {
MessageDigest md;
try {
md = MessageDigest.getInstance(digest);
@@ -188,8 +213,7 @@ public class MemorizingTrustManager {
StringBuffer si = new StringBuffer();
for (int i = 0; i < data.length; i++) {
si.append(String.format("%02x", data[i]));
- if (i < data.length - 1)
- si.append(":");
+ if (i < data.length - 1) si.append(":");
}
return si.toString();
}
@@ -220,20 +244,22 @@ public class MemorizingTrustManager {
}
}
- void init(final Context m) {
- master = m;
- masterHandler = new Handler(m.getMainLooper());
- notificationManager = (NotificationManager) master.getSystemService(Context.NOTIFICATION_SERVICE);
+ void init(final Context context) {
+ master = context;
+ masterHandler = new Handler(context.getMainLooper());
+ notificationManager =
+ (NotificationManager) master.getSystemService(Context.NOTIFICATION_SERVICE);
Application app;
- if (m instanceof Application) {
- app = (Application) m;
- } else if (m instanceof Service) {
- app = ((Service) m).getApplication();
- } else if (m instanceof AppCompatActivity) {
- app = ((AppCompatActivity) m).getApplication();
+ if (context instanceof Application) {
+ app = (Application) context;
+ } else if (context instanceof Service) {
+ app = ((Service) context).getApplication();
+ } else if (context instanceof AppCompatActivity) {
+ app = ((AppCompatActivity) context).getApplication();
} else
- throw new ClassCastException("MemorizingTrustManager context must be either Activity or Service!");
+ throw new ClassCastException(
+ "MemorizingTrustManager context must be either Activity or Service!");
File dir = app.getDir(KEYSTORE_DIR, Context.MODE_PRIVATE);
keyStoreFile = new File(dir + File.separator + KEYSTORE_FILE);
@@ -260,12 +286,9 @@ public class MemorizingTrustManager {
/**
* Removes the given certificate from MTMs key store.
*
- *
- * WARNING: this does not immediately invalidate the certificate. It is
- * well possible that (a) data is transmitted over still existing connections or
- * (b) new connections are created using TLS renegotiation, without a new cert
- * check.
- *
+ * WARNING: this does not immediately invalidate the certificate. It is well possible
+ * that (a) data is transmitted over still existing connections or (b) new connections are
+ * created using TLS renegotiation, without a new cert check.
*
* @param alias the certificate's alias as returned by {@link #getCertificates()}.
* @throws KeyStoreException if the certificate could not be deleted.
@@ -275,20 +298,21 @@ public class MemorizingTrustManager {
keyStoreUpdated();
}
- X509TrustManager getTrustManager(KeyStore ks) {
+ private X509TrustManager getTrustManager(final KeyStore keyStore) {
+ Preconditions.checkNotNull(keyStore);
try {
TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
- tmf.init(ks);
+ tmf.init(keyStore);
for (TrustManager t : tmf.getTrustManagers()) {
if (t instanceof X509TrustManager) {
return (X509TrustManager) t;
}
}
- } catch (Exception e) {
+ } catch (final Exception e) {
// Here, we are covering up errors. It might be more useful
// however to throw them out of the constructor so the
// embedding app knows something went wrong.
- LOGGER.log(Level.SEVERE, "getTrustManager(" + ks + ")", e);
+ LOGGER.log(Level.SEVERE, "getTrustManager(" + keyStore + ")", e);
}
return null;
}
@@ -361,45 +385,60 @@ public class MemorizingTrustManager {
}
}
-
- private void checkCertTrusted(X509Certificate[] chain, String authType, String domain, boolean isServer, boolean interactive)
+ private void checkCertTrusted(
+ X509Certificate[] chain,
+ String authType,
+ String domain,
+ boolean isServer,
+ boolean interactive)
throws CertificateException {
- LOGGER.log(Level.FINE, "checkCertTrusted(" + chain + ", " + authType + ", " + isServer + ")");
+ LOGGER.log(
+ Level.FINE, "checkCertTrusted(" + chain + ", " + authType + ", " + isServer + ")");
try {
LOGGER.log(Level.FINE, "checkCertTrusted: trying appTrustManager");
- if (isServer)
- appTrustManager.checkServerTrusted(chain, authType);
- else
- appTrustManager.checkClientTrusted(chain, authType);
+ if (isServer) appTrustManager.checkServerTrusted(chain, authType);
+ else appTrustManager.checkClientTrusted(chain, authType);
} catch (final CertificateException ae) {
LOGGER.log(Level.FINER, "checkCertTrusted: appTrustManager failed", ae);
if (isCertKnown(chain[0])) {
- LOGGER.log(Level.INFO, "checkCertTrusted: accepting cert already stored in keystore");
+ LOGGER.log(
+ Level.INFO, "checkCertTrusted: accepting cert already stored in keystore");
return;
}
try {
- if (defaultTrustManager == null)
- throw ae;
+ if (defaultTrustManager == null) throw ae;
LOGGER.log(Level.FINE, "checkCertTrusted: trying defaultTrustManager");
- if (isServer)
- defaultTrustManager.checkServerTrusted(chain, authType);
- else
- defaultTrustManager.checkClientTrusted(chain, authType);
+ if (isServer) defaultTrustManager.checkServerTrusted(chain, authType);
+ else defaultTrustManager.checkClientTrusted(chain, authType);
} catch (final CertificateException e) {
- final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(master);
- final boolean trustSystemCAs = !preferences.getBoolean("dont_trust_system_cas", false);
- if (domain != null && isServer && trustSystemCAs && !isIp(domain) && !domain.endsWith(".onion")) {
+ final SharedPreferences preferences =
+ PreferenceManager.getDefaultSharedPreferences(master);
+ final boolean trustSystemCAs =
+ !preferences.getBoolean("dont_trust_system_cas", false);
+ if (domain != null
+ && isServer
+ && trustSystemCAs
+ && !isIp(domain)
+ && !domain.endsWith(".onion")) {
final String hash = getBase64Hash(chain[0], "SHA-256");
final List fingerprints = getPoshFingerprints(domain);
if (hash != null && fingerprints.size() > 0) {
if (fingerprints.contains(hash)) {
- Log.d(Config.LOGTAG, "trusted cert fingerprint of " + domain + " via posh");
+ Log.d(
+ Config.LOGTAG,
+ "trusted cert fingerprint of " + domain + " via posh");
return;
} else {
- Log.d(Config.LOGTAG, "fingerprint " + hash + " not found in " + fingerprints);
+ Log.d(
+ Config.LOGTAG,
+ "fingerprint " + hash + " not found in " + fingerprints);
}
if (getPoshCacheFile(domain).delete()) {
- Log.d(Config.LOGTAG, "deleted posh file for " + domain + " after not being able to verify");
+ Log.d(
+ Config.LOGTAG,
+ "deleted posh file for "
+ + domain
+ + " after not being able to verify");
}
}
}
@@ -422,17 +461,25 @@ public class MemorizingTrustManager {
}
private List getPoshFingerprintsFromServer(String domain) {
- return getPoshFingerprintsFromServer(domain, "https://" + domain + "/.well-known/posh/xmpp-client.json", -1, true);
+ return getPoshFingerprintsFromServer(
+ domain, "https://" + domain + "/.well-known/posh/xmpp-client.json", -1, true);
}
- private List getPoshFingerprintsFromServer(String domain, String url, int maxTtl, boolean followUrl) {
+ private List getPoshFingerprintsFromServer(
+ String domain, String url, int maxTtl, boolean followUrl) {
Log.d(Config.LOGTAG, "downloading json for " + domain + " from " + url);
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(master);
- final boolean useTor = QuickConversationsService.isConversations() && preferences.getBoolean("use_tor", master.getResources().getBoolean(R.bool.use_tor));
+ final boolean useTor =
+ QuickConversationsService.isConversations()
+ && preferences.getBoolean(
+ "use_tor", master.getResources().getBoolean(R.bool.use_tor));
try {
final List results = new ArrayList<>();
final InputStream inputStream = HttpConnectionManager.open(url, useTor);
- final String body = CharStreams.toString(new InputStreamReader(ByteStreams.limit(inputStream,10_000), Charsets.UTF_8));
+ final String body =
+ CharStreams.toString(
+ new InputStreamReader(
+ ByteStreams.limit(inputStream, 10_000), Charsets.UTF_8));
final JSONObject jsonObject = new JSONObject(body);
int expires = jsonObject.getInt("expires");
if (expires <= 0) {
@@ -459,7 +506,7 @@ public class MemorizingTrustManager {
writeFingerprintsToCache(domain, results, 1000L * expires + System.currentTimeMillis());
return results;
} catch (final Exception e) {
- Log.d(Config.LOGTAG, "error fetching posh",e);
+ Log.d(Config.LOGTAG, "error fetching posh", e);
return new ArrayList<>();
}
}
@@ -489,7 +536,8 @@ public class MemorizingTrustManager {
final File file = getPoshCacheFile(domain);
try {
final InputStream inputStream = new FileInputStream(file);
- final String json = CharStreams.toString(new InputStreamReader(inputStream, Charsets.UTF_8));
+ final String json =
+ CharStreams.toString(new InputStreamReader(inputStream, Charsets.UTF_8));
final JSONObject jsonObject = new JSONObject(json);
long expires = jsonObject.getLong("expires");
long expiresIn = expires - System.currentTimeMillis();
@@ -514,7 +562,9 @@ public class MemorizingTrustManager {
}
private X509Certificate[] getAcceptedIssuers() {
- return defaultTrustManager == null ? new X509Certificate[0] : defaultTrustManager.getAcceptedIssuers();
+ return defaultTrustManager == null
+ ? new X509Certificate[0]
+ : defaultTrustManager.getAcceptedIssuers();
}
private int createDecisionId(MTMDecision d) {
@@ -527,7 +577,8 @@ public class MemorizingTrustManager {
return myId;
}
- private void certDetails(final StringBuffer si, final X509Certificate c, final boolean showValidFor) {
+ private void certDetails(
+ final StringBuffer si, final X509Certificate c, final boolean showValidFor) {
si.append("\n");
if (showValidFor) {
@@ -564,8 +615,7 @@ public class MemorizingTrustManager {
// not found", so we use string comparison.
if (NO_TRUST_ANCHOR.equals(e.getMessage())) {
si.append(master.getString(R.string.mtm_trust_anchor));
- } else
- si.append(e.getLocalizedMessage());
+ } else si.append(e.getLocalizedMessage());
si.append("\n");
}
si.append("\n");
@@ -573,7 +623,7 @@ public class MemorizingTrustManager {
si.append("\n\n");
si.append(master.getString(R.string.mtm_cert_details));
si.append('\n');
- for(int i = 0; i < chain.length; ++i) {
+ for (int i = 0; i < chain.length; ++i) {
certDetails(si, chain[i], i == 0);
}
return si.toString();
@@ -593,24 +643,25 @@ public class MemorizingTrustManager {
MTMDecision choice = new MTMDecision();
final int myId = createDecisionId(choice);
- masterHandler.post(new Runnable() {
- public void run() {
- Intent ni = new Intent(master, MemorizingActivity.class);
- ni.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- ni.setData(Uri.parse(MemorizingTrustManager.class.getName() + "/" + myId));
- ni.putExtra(DECISION_INTENT_ID, myId);
- ni.putExtra(DECISION_INTENT_CERT, message);
- ni.putExtra(DECISION_TITLE_ID, titleId);
+ masterHandler.post(
+ new Runnable() {
+ public void run() {
+ Intent ni = new Intent(master, MemorizingActivity.class);
+ ni.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ ni.setData(Uri.parse(MemorizingTrustManager.class.getName() + "/" + myId));
+ ni.putExtra(DECISION_INTENT_ID, myId);
+ ni.putExtra(DECISION_INTENT_CERT, message);
+ ni.putExtra(DECISION_TITLE_ID, titleId);
- // we try to directly start the activity and fall back to
- // making a notification
- try {
- getUI().startActivity(ni);
- } catch (Exception e) {
- LOGGER.log(Level.FINE, "startActivity(MemorizingActivity)", e);
- }
- }
- });
+ // we try to directly start the activity and fall back to
+ // making a notification
+ try {
+ getUI().startActivity(ni);
+ } catch (Exception e) {
+ LOGGER.log(Level.FINE, "startActivity(MemorizingActivity)", e);
+ }
+ }
+ });
LOGGER.log(Level.FINE, "openDecisions: " + openDecisions + ", waiting on " + myId);
try {
@@ -661,7 +712,8 @@ public class MemorizingTrustManager {
}
@Override
- public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ public void checkClientTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException {
MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, false, false);
}
@@ -675,7 +727,6 @@ public class MemorizingTrustManager {
public X509Certificate[] getAcceptedIssuers() {
return MemorizingTrustManager.this.getAcceptedIssuers();
}
-
}
private class InteractiveMemorizingTrustManager implements X509TrustManager {
@@ -686,7 +737,8 @@ public class MemorizingTrustManager {
}
@Override
- public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
+ public void checkClientTrusted(X509Certificate[] chain, String authType)
+ throws CertificateException {
MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, false, true);
}
diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java
index 10cfdcb04..44d16a0d7 100644
--- a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java
+++ b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java
@@ -4,7 +4,7 @@ import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
import android.util.Log;
-import org.jetbrains.annotations.NotNull;
+import androidx.annotation.NonNull;
import java.math.BigInteger;
import java.util.ArrayList;
@@ -286,10 +286,18 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
if (conversation != null) {
conversation.sort();
conversation.setHasMessagesLeftOnServer(!done);
+ final var displayState = conversation.getDisplayState();
+ if (displayState != null) {
+ mXmppConnectionService.markReadUpToStanzaId(conversation, displayState);
+ }
} else {
- for (Conversation tmp : this.mXmppConnectionService.getConversations()) {
+ for (final Conversation tmp : this.mXmppConnectionService.getConversations()) {
if (tmp.getAccount() == query.getAccount()) {
tmp.sort();
+ final var displayState = tmp.getDisplayState();
+ if (displayState != null) {
+ mXmppConnectionService.markReadUpToStanzaId(tmp, displayState);
+ }
}
}
}
@@ -636,7 +644,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
}
}
- @NotNull
+ @NonNull
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java
index 0742215d1..7027ee2cc 100644
--- a/src/main/java/eu/siacs/conversations/services/NotificationService.java
+++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java
@@ -2,6 +2,7 @@ package eu.siacs.conversations.services;
import static eu.siacs.conversations.utils.Compatibility.s;
+import android.Manifest;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationChannelGroup;
@@ -10,17 +11,17 @@ import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
+import android.content.pm.PackageManager;
import android.content.pm.ShortcutManager;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Typeface;
import android.media.AudioAttributes;
-import android.media.Ringtone;
+import android.media.AudioManager;
import android.media.RingtoneManager;
import android.net.Uri;
import android.os.Build;
import android.os.SystemClock;
-import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.text.SpannableString;
@@ -28,7 +29,9 @@ import android.text.style.StyleSpan;
import android.util.DisplayMetrics;
import android.util.Log;
+import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
+import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationCompat.BigPictureStyle;
import androidx.core.app.NotificationCompat.Builder;
@@ -41,29 +44,14 @@ import androidx.core.graphics.drawable.IconCompat;
import com.google.android.material.color.MaterialColors;
import com.google.common.base.Joiner;
+import com.google.common.base.Optional;
+import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
+import com.google.common.primitives.Ints;
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-
+import eu.siacs.conversations.AppSettings;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
@@ -75,7 +63,6 @@ import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.ui.ConversationsActivity;
import eu.siacs.conversations.ui.EditAccountActivity;
import eu.siacs.conversations.ui.RtpSessionActivity;
-import eu.siacs.conversations.ui.TimePreference;
import eu.siacs.conversations.utils.AccountUtils;
import eu.siacs.conversations.utils.Compatibility;
import eu.siacs.conversations.utils.GeoHelper;
@@ -86,16 +73,28 @@ import eu.siacs.conversations.xmpp.XmppConnection;
import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection;
import eu.siacs.conversations.xmpp.jingle.Media;
-public class NotificationService {
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
- private static final ScheduledExecutorService SCHEDULED_EXECUTOR_SERVICE =
- Executors.newSingleThreadScheduledExecutor();
+public class NotificationService {
public static final Object CATCHUP_LOCK = new Object();
private static final int LED_COLOR = 0xff0000ff;
- private static final long[] CALL_PATTERN = {0, 500, 300, 600};
+ private static final long[] CALL_PATTERN = {0, 500, 300, 600, 3000};
private static final String MESSAGES_GROUP = "eu.siacs.conversations.messages";
private static final String MISSED_CALLS_GROUP = "eu.siacs.conversations.missed_calls";
@@ -107,6 +106,8 @@ public class NotificationService {
public static final int ONGOING_CALL_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 10;
public static final int MISSED_CALL_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 12;
private static final int DELIVERY_FAILED_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 13;
+ public static final int ONGOING_VIDEO_TRANSCODING_NOTIFICATION_ID =
+ NOTIFICATION_ID_MULTIPLIER * 14;
private final XmppConnectionService mXmppConnectionService;
private final LinkedHashMap> notifications = new LinkedHashMap<>();
private final HashMap mBacklogMessageCounter = new HashMap<>();
@@ -117,8 +118,9 @@ public class NotificationService {
private long mLastNotification;
private static final String INCOMING_CALLS_NOTIFICATION_CHANNEL = "incoming_calls_channel";
- private Ringtone currentlyPlayingRingtone = null;
- private ScheduledFuture> vibrationFuture;
+ private static final String INCOMING_CALLS_NOTIFICATION_CHANNEL_PREFIX =
+ "incoming_calls_channel#";
+ private static final String MESSAGES_NOTIFICATION_CHANNEL = "messages";
NotificationService(final XmppConnectionService service) {
this.mXmppConnectionService = service;
@@ -159,6 +161,7 @@ public class NotificationService {
notificationManager.deleteNotificationChannel("export");
notificationManager.deleteNotificationChannel("incoming_calls");
+ notificationManager.deleteNotificationChannel(INCOMING_CALLS_NOTIFICATION_CHANNEL);
notificationManager.createNotificationChannelGroup(
new NotificationChannelGroup(
@@ -209,19 +212,7 @@ public class NotificationService {
exportChannel.setGroup("status");
notificationManager.createNotificationChannel(exportChannel);
- final NotificationChannel incomingCallsChannel =
- new NotificationChannel(
- INCOMING_CALLS_NOTIFICATION_CHANNEL,
- c.getString(R.string.incoming_calls_channel_name),
- NotificationManager.IMPORTANCE_HIGH);
- incomingCallsChannel.setSound(null, null);
- incomingCallsChannel.setShowBadge(false);
- incomingCallsChannel.setLightColor(LED_COLOR);
- incomingCallsChannel.enableLights(true);
- incomingCallsChannel.setGroup("calls");
- incomingCallsChannel.setBypassDnd(true);
- incomingCallsChannel.enableVibration(false);
- notificationManager.createNotificationChannel(incomingCallsChannel);
+ createInitialIncomingCallChannelIfNecessary(c);
final NotificationChannel ongoingCallsChannel =
new NotificationChannel(
@@ -246,7 +237,7 @@ public class NotificationService {
final NotificationChannel messagesChannel =
new NotificationChannel(
- "messages",
+ MESSAGES_NOTIFICATION_CHANNEL,
c.getString(R.string.messages_channel_name),
NotificationManager.IMPORTANCE_HIGH);
messagesChannel.setShowBadge(true);
@@ -254,7 +245,7 @@ public class NotificationService {
RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION),
new AudioAttributes.Builder()
.setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
- .setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT)
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
.build());
messagesChannel.setLightColor(LED_COLOR);
final int dat = 70;
@@ -277,20 +268,6 @@ public class NotificationService {
silentMessagesChannel.setGroup("chats");
notificationManager.createNotificationChannel(silentMessagesChannel);
- final NotificationChannel quietHoursChannel =
- new NotificationChannel(
- "quiet_hours",
- c.getString(R.string.title_pref_quiet_hours),
- NotificationManager.IMPORTANCE_LOW);
- quietHoursChannel.setShowBadge(true);
- quietHoursChannel.setLightColor(LED_COLOR);
- quietHoursChannel.enableLights(true);
- quietHoursChannel.setGroup("chats");
- quietHoursChannel.enableVibration(false);
- quietHoursChannel.setSound(null, null);
-
- notificationManager.createNotificationChannel(quietHoursChannel);
-
final NotificationChannel deliveryFailedChannel =
new NotificationChannel(
"delivery_failed",
@@ -307,6 +284,98 @@ public class NotificationService {
notificationManager.createNotificationChannel(deliveryFailedChannel);
}
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ private static void createInitialIncomingCallChannelIfNecessary(final Context context) {
+ final var currentIteration = getCurrentIncomingCallChannelIteration(context);
+ if (currentIteration.isPresent()) {
+ return;
+ }
+ createInitialIncomingCallChannel(context);
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ public static Optional getCurrentIncomingCallChannelIteration(final Context context) {
+ final var notificationManager = context.getSystemService(NotificationManager.class);
+ for (final NotificationChannel channel : notificationManager.getNotificationChannels()) {
+ final String id = channel.getId();
+ if (Strings.isNullOrEmpty(id)) {
+ continue;
+ }
+ if (id.startsWith(INCOMING_CALLS_NOTIFICATION_CHANNEL_PREFIX)) {
+ final var parts = Splitter.on('#').splitToList(id);
+ if (parts.size() == 2) {
+ final var iteration = Ints.tryParse(parts.get(1));
+ if (iteration != null) {
+ return Optional.of(iteration);
+ }
+ }
+ }
+ }
+ return Optional.absent();
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ public static Optional getCurrentIncomingCallChannel(
+ final Context context) {
+ final var iteration = getCurrentIncomingCallChannelIteration(context);
+ return iteration.transform(
+ i -> {
+ final var notificationManager =
+ context.getSystemService(NotificationManager.class);
+ return notificationManager.getNotificationChannel(
+ INCOMING_CALLS_NOTIFICATION_CHANNEL_PREFIX + i);
+ });
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ private static void createInitialIncomingCallChannel(final Context context) {
+ final var appSettings = new AppSettings(context);
+ final var ringtoneUri = appSettings.getRingtone();
+ createIncomingCallChannel(context, ringtoneUri, 0);
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ public static void recreateIncomingCallChannel(final Context context, final Uri ringtone) {
+ final var currentIteration = getCurrentIncomingCallChannelIteration(context);
+ final int nextIteration;
+ if (currentIteration.isPresent()) {
+ final var notificationManager = context.getSystemService(NotificationManager.class);
+ notificationManager.deleteNotificationChannel(
+ INCOMING_CALLS_NOTIFICATION_CHANNEL_PREFIX + currentIteration.get());
+ nextIteration = currentIteration.get() + 1;
+ } else {
+ nextIteration = 0;
+ }
+ createIncomingCallChannel(context, ringtone, nextIteration);
+ }
+
+ @RequiresApi(api = Build.VERSION_CODES.O)
+ private static void createIncomingCallChannel(
+ final Context context, final Uri ringtoneUri, final int iteration) {
+ final var notificationManager = context.getSystemService(NotificationManager.class);
+ final var id = INCOMING_CALLS_NOTIFICATION_CHANNEL_PREFIX + iteration;
+ Log.d(Config.LOGTAG, "creating incoming call channel with id " + id);
+ final NotificationChannel incomingCallsChannel =
+ new NotificationChannel(
+ id,
+ context.getString(R.string.incoming_calls_channel_name),
+ NotificationManager.IMPORTANCE_HIGH);
+ incomingCallsChannel.setSound(
+ ringtoneUri,
+ new AudioAttributes.Builder()
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .build());
+ incomingCallsChannel.setShowBadge(false);
+ incomingCallsChannel.setLightColor(LED_COLOR);
+ incomingCallsChannel.enableLights(true);
+ incomingCallsChannel.setGroup("calls");
+ incomingCallsChannel.setBypassDnd(true);
+ incomingCallsChannel.enableVibration(true);
+ incomingCallsChannel.setVibrationPattern(CALL_PATTERN);
+ notificationManager.createNotificationChannel(incomingCallsChannel);
+ }
+
private boolean notifyMessage(final Message message) {
final Conversation conversation = (Conversation) message.getConversation();
return message.getStatus() == Message.STATUS_RECEIVED
@@ -326,28 +395,6 @@ public class NotificationService {
"notifications_from_strangers", R.bool.notifications_from_strangers);
}
- private boolean isQuietHours() {
- if (!mXmppConnectionService.getBooleanPreference(
- "enable_quiet_hours", R.bool.enable_quiet_hours)) {
- return false;
- }
- final SharedPreferences preferences =
- PreferenceManager.getDefaultSharedPreferences(mXmppConnectionService);
- final long startTime =
- TimePreference.minutesToTimestamp(
- preferences.getLong("quiet_hours_start", TimePreference.DEFAULT_VALUE));
- final long endTime =
- TimePreference.minutesToTimestamp(
- preferences.getLong("quiet_hours_end", TimePreference.DEFAULT_VALUE));
- final long nowTime = Calendar.getInstance().getTimeInMillis();
-
- if (endTime < startTime) {
- return nowTime > startTime || nowTime < endTime;
- } else {
- return nowTime > startTime && nowTime < endTime;
- }
- }
-
public void pushFromBacklog(final Message message) {
if (notifyMessage(message)) {
synchronized (notifications) {
@@ -502,54 +549,13 @@ public class NotificationService {
public synchronized void startRinging(
final AbstractJingleConnection.Id id, final Set media) {
- showIncomingCallNotification(id, media);
- final NotificationManager notificationManager =
- (NotificationManager)
- mXmppConnectionService.getSystemService(Context.NOTIFICATION_SERVICE);
- final int currentInterruptionFilter;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && notificationManager != null) {
- currentInterruptionFilter = notificationManager.getCurrentInterruptionFilter();
- } else {
- currentInterruptionFilter = 1; // INTERRUPTION_FILTER_ALL
- }
- if (currentInterruptionFilter != 1) {
- Log.d(
- Config.LOGTAG,
- "do not ring or vibrate because interruption filter has been set to "
- + currentInterruptionFilter);
- return;
- }
- final ScheduledFuture> currentVibrationFuture = this.vibrationFuture;
- this.vibrationFuture =
- SCHEDULED_EXECUTOR_SERVICE.scheduleAtFixedRate(
- new VibrationRunnable(), 0, 3, TimeUnit.SECONDS);
- if (currentVibrationFuture != null) {
- currentVibrationFuture.cancel(true);
- }
- final SharedPreferences preferences =
- PreferenceManager.getDefaultSharedPreferences(mXmppConnectionService);
- final Resources resources = mXmppConnectionService.getResources();
- final String ringtonePreference =
- preferences.getString(
- "call_ringtone", resources.getString(R.string.incoming_call_ringtone));
- if (Strings.isNullOrEmpty(ringtonePreference)) {
- Log.d(Config.LOGTAG, "ringtone has been set to none");
- return;
- }
- final Uri uri = Uri.parse(ringtonePreference);
- this.currentlyPlayingRingtone = RingtoneManager.getRingtone(mXmppConnectionService, uri);
- if (this.currentlyPlayingRingtone == null) {
- Log.d(Config.LOGTAG, "unable to find ringtone for uri " + uri);
- return;
- }
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
- this.currentlyPlayingRingtone.setLooping(true);
- }
- this.currentlyPlayingRingtone.play();
+ showIncomingCallNotification(id, media, false);
}
private void showIncomingCallNotification(
- final AbstractJingleConnection.Id id, final Set media) {
+ final AbstractJingleConnection.Id id,
+ final Set media,
+ final boolean onlyAlertOnce) {
final Intent fullScreenIntent =
new Intent(mXmppConnectionService, RtpSessionActivity.class);
fullScreenIntent.putExtra(
@@ -559,15 +565,27 @@ public class NotificationService {
fullScreenIntent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, id.sessionId);
fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
fullScreenIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+ final int channelIteration;
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+ channelIteration = getCurrentIncomingCallChannelIteration(mXmppConnectionService).or(0);
+ } else {
+ channelIteration = 0;
+ }
+ final var channelId = INCOMING_CALLS_NOTIFICATION_CHANNEL_PREFIX + channelIteration;
+ Log.d(
+ Config.LOGTAG,
+ "showing incoming call notification on channel "
+ + channelId
+ + ", onlyAlertOnce="
+ + onlyAlertOnce);
final NotificationCompat.Builder builder =
- new NotificationCompat.Builder(
- mXmppConnectionService, INCOMING_CALLS_NOTIFICATION_CHANNEL);
+ new NotificationCompat.Builder(mXmppConnectionService, channelId);
if (media.contains(Media.VIDEO)) {
- builder.setSmallIcon(R.drawable.ic_videocam_white_24dp);
+ builder.setSmallIcon(R.drawable.ic_videocam_24dp);
builder.setContentTitle(
mXmppConnectionService.getString(R.string.rtp_state_incoming_video_call));
} else {
- builder.setSmallIcon(R.drawable.ic_call_white_24dp);
+ builder.setSmallIcon(R.drawable.ic_call_24dp);
builder.setContentTitle(
mXmppConnectionService.getString(R.string.rtp_state_incoming_call));
}
@@ -580,11 +598,20 @@ public class NotificationService {
if (systemAccount != null) {
builder.addPerson(systemAccount.toString());
}
+ if (!onlyAlertOnce) {
+ final var appSettings = new AppSettings(mXmppConnectionService);
+ final var ringtone = appSettings.getRingtone();
+ if (ringtone != null) {
+ builder.setSound(ringtone, AudioManager.STREAM_RING);
+ }
+ builder.setVibrate(CALL_PATTERN);
+ }
+ builder.setOnlyAlertOnce(onlyAlertOnce);
builder.setContentText(id.account.getRoster().getContact(id.with).getDisplayName());
builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC);
builder.setPriority(NotificationCompat.PRIORITY_HIGH);
builder.setCategory(NotificationCompat.CATEGORY_CALL);
- PendingIntent pendingIntent = createPendingRtpSession(id, Intent.ACTION_VIEW, 101);
+ final PendingIntent pendingIntent = createPendingRtpSession(id, Intent.ACTION_VIEW, 101);
builder.setFullScreenIntent(pendingIntent, true);
builder.setContentIntent(pendingIntent); // old androids need this?
builder.setOngoing(true);
@@ -606,6 +633,10 @@ public class NotificationService {
.build());
modifyIncomingCall(builder);
final Notification notification = builder.build();
+ notification.audioAttributes = new AudioAttributes.Builder()
+ .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+ .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE)
+ .build();
notification.flags = notification.flags | Notification.FLAG_INSISTENT;
notify(INCOMING_CALL_NOTIFICATION_ID, notification);
}
@@ -616,7 +647,7 @@ public class NotificationService {
final NotificationCompat.Builder builder =
new NotificationCompat.Builder(mXmppConnectionService, "ongoing_calls");
if (ongoingCall.media.contains(Media.VIDEO)) {
- builder.setSmallIcon(R.drawable.ic_videocam_white_24dp);
+ builder.setSmallIcon(R.drawable.ic_videocam_24dp);
if (ongoingCall.reconnecting) {
builder.setContentTitle(
mXmppConnectionService.getString(R.string.reconnecting_video_call));
@@ -625,7 +656,7 @@ public class NotificationService {
mXmppConnectionService.getString(R.string.ongoing_video_call));
}
} else {
- builder.setSmallIcon(R.drawable.ic_call_white_24dp);
+ builder.setSmallIcon(R.drawable.ic_call_24dp);
if (ongoingCall.reconnecting) {
builder.setContentTitle(
mXmppConnectionService.getString(R.string.reconnecting_call));
@@ -670,25 +701,25 @@ public class NotificationService {
}
public void cancelIncomingCallNotification() {
- stopSoundAndVibration();
cancel(INCOMING_CALL_NOTIFICATION_ID);
}
public boolean stopSoundAndVibration() {
- int stopped = 0;
- if (this.currentlyPlayingRingtone != null) {
- if (this.currentlyPlayingRingtone.isPlaying()) {
- Log.d(Config.LOGTAG, "stop playing ring tone");
- ++stopped;
- }
- this.currentlyPlayingRingtone.stop();
+ final var jingleRtpConnection =
+ mXmppConnectionService.getJingleConnectionManager().getOngoingRtpConnection();
+ if (jingleRtpConnection == null) {
+ return false;
}
- if (this.vibrationFuture != null && !this.vibrationFuture.isCancelled()) {
- Log.d(Config.LOGTAG, "stop vibration");
- this.vibrationFuture.cancel(true);
- ++stopped;
+ final var notificationManager = mXmppConnectionService.getSystemService(NotificationManager.class);
+ if (Iterables.any(
+ Arrays.asList(notificationManager.getActiveNotifications()),
+ n -> n.getId() == INCOMING_CALL_NOTIFICATION_ID)) {
+ Log.d(Config.LOGTAG, "stopping sound and vibration for incoming call notification");
+ showIncomingCallNotification(
+ jingleRtpConnection.getId(), jingleRtpConnection.getMedia(), true);
+ return true;
}
- return stopped > 0;
+ return false;
}
public static void cancelIncomingCallNotification(final Context context) {
@@ -779,7 +810,8 @@ public class NotificationService {
public void clearMissedCall(final Message message) {
synchronized (mMissedCalls) {
- final Iterator> iterator = mMissedCalls.entrySet().iterator();
+ final Iterator> iterator =
+ mMissedCalls.entrySet().iterator();
while (iterator.hasNext()) {
final Map.Entry entry = iterator.next();
final Conversational conversational = entry.getKey();
@@ -787,7 +819,10 @@ public class NotificationService {
if (conversational.getUuid().equals(message.getConversation().getUuid())) {
if (missedCallsInfo.removeMissedCall()) {
cancel(conversational.getUuid(), MISSED_CALL_NOTIFICATION_ID);
- Log.d(Config.LOGTAG,conversational.getAccount().getJid().asBareJid()+": dismissed missed call because call was picked up on other device");
+ Log.d(
+ Config.LOGTAG,
+ conversational.getAccount().getJid().asBareJid()
+ + ": dismissed missed call because call was picked up on other device");
iterator.remove();
}
}
@@ -820,7 +855,7 @@ public class NotificationService {
}
private void markAsReadIfHasDirectReply(final ArrayList messages) {
- if (messages != null && messages.size() > 0) {
+ if (messages != null && !messages.isEmpty()) {
Message last = messages.get(messages.size() - 1);
if (last.getStatus() != Message.STATUS_RECEIVED) {
if (mXmppConnectionService.markRead((Conversation) last.getConversation(), false)) {
@@ -854,8 +889,6 @@ public class NotificationService {
final SharedPreferences preferences =
PreferenceManager.getDefaultSharedPreferences(mXmppConnectionService);
- final boolean quiteHours = isQuietHours();
-
final boolean notifyOnlyOneChild =
notify
&& conversations != null
@@ -863,7 +896,7 @@ public class NotificationService {
== 1; // if this check is changed to > 0 catchup messages will
// create one notification per conversation
- if (notifications.size() == 0) {
+ if (notifications.isEmpty()) {
cancel(NOTIFICATION_ID);
} else {
if (notify) {
@@ -872,29 +905,27 @@ public class NotificationService {
final Builder mBuilder;
if (notifications.size() == 1 && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
mBuilder =
- buildSingleConversations(
- notifications.values().iterator().next(), notify, quiteHours);
- modifyForSoundVibrationAndLight(mBuilder, notify, quiteHours, preferences);
+ buildSingleConversations(notifications.values().iterator().next(), notify);
+ modifyForSoundVibrationAndLight(mBuilder, notify, preferences);
notify(NOTIFICATION_ID, mBuilder.build());
} else {
- mBuilder = buildMultipleConversation(notify, quiteHours);
+ mBuilder = buildMultipleConversation(notify);
if (notifyOnlyOneChild) {
mBuilder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN);
}
- modifyForSoundVibrationAndLight(mBuilder, notify, quiteHours, preferences);
+ modifyForSoundVibrationAndLight(mBuilder, notify, preferences);
if (!summaryOnly) {
for (Map.Entry> entry : notifications.entrySet()) {
String uuid = entry.getKey();
final boolean notifyThis =
notifyOnlyOneChild ? conversations.contains(uuid) : notify;
Builder singleBuilder =
- buildSingleConversations(entry.getValue(), notifyThis, quiteHours);
+ buildSingleConversations(entry.getValue(), notifyThis);
if (!notifyOnlyOneChild) {
singleBuilder.setGroupAlertBehavior(
NotificationCompat.GROUP_ALERT_SUMMARY);
}
- modifyForSoundVibrationAndLight(
- singleBuilder, notifyThis, quiteHours, preferences);
+ modifyForSoundVibrationAndLight(singleBuilder, notifyThis, preferences);
singleBuilder.setGroup(MESSAGES_GROUP);
setNotificationColor(singleBuilder);
notify(entry.getKey(), NOTIFICATION_ID, singleBuilder.build());
@@ -931,21 +962,24 @@ public class NotificationService {
}
private void modifyForSoundVibrationAndLight(
- Builder mBuilder, boolean notify, boolean quietHours, SharedPreferences preferences) {
+ final Builder mBuilder, final boolean notify, final SharedPreferences preferences) {
final Resources resources = mXmppConnectionService.getResources();
final String ringtone =
preferences.getString(
- "notification_ringtone",
+ AppSettings.NOTIFICATION_RINGTONE,
resources.getString(R.string.notification_ringtone));
final boolean vibrate =
preferences.getBoolean(
- "vibrate_on_notification",
+ AppSettings.NOTIFICATION_VIBRATE,
resources.getBoolean(R.bool.vibrate_on_notification));
- final boolean led = preferences.getBoolean("led", resources.getBoolean(R.bool.led));
+ final boolean led =
+ preferences.getBoolean(
+ AppSettings.NOTIFICATION_LED, resources.getBoolean(R.bool.led));
final boolean headsup =
preferences.getBoolean(
- "notification_headsup", resources.getBoolean(R.bool.headsup_notifications));
- if (notify && !quietHours) {
+ AppSettings.NOTIFICATION_HEADS_UP,
+ resources.getBoolean(R.bool.headsup_notifications));
+ if (notify) {
if (vibrate) {
final int dat = 70;
final long[] pattern = {0, 3 * dat, dat, dat};
@@ -1111,11 +1145,11 @@ public class NotificationService {
setNotificationColor(builder);
}
- private Builder buildMultipleConversation(final boolean notify, final boolean quietHours) {
+ private Builder buildMultipleConversation(final boolean notify) {
final Builder mBuilder =
new NotificationCompat.Builder(
mXmppConnectionService,
- quietHours ? "quiet_hours" : (notify ? "messages" : "silent_messages"));
+ notify ? MESSAGES_NOTIFICATION_CHANNEL : "silent_messages");
final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle();
style.setBigContentTitle(
mXmppConnectionService
@@ -1180,161 +1214,162 @@ public class NotificationService {
}
private Builder buildSingleConversations(
- final ArrayList messages, final boolean notify, final boolean quietHours) {
- final Builder mBuilder =
- new NotificationCompat.Builder(
- mXmppConnectionService,
- quietHours ? "quiet_hours" : (notify ? "messages" : "silent_messages"));
- if (messages.size() >= 1) {
- final Conversation conversation = (Conversation) messages.get(0).getConversation();
- mBuilder.setLargeIcon(
+ final ArrayList messages, final boolean notify) {
+ final var channel = notify ? MESSAGES_NOTIFICATION_CHANNEL : "silent_messages";
+ final Builder notificationBuilder =
+ new NotificationCompat.Builder(mXmppConnectionService, channel);
+ if (messages.isEmpty()) {
+ return notificationBuilder;
+ }
+ final Conversation conversation = (Conversation) messages.get(0).getConversation();
+ notificationBuilder.setLargeIcon(
+ mXmppConnectionService
+ .getAvatarService()
+ .get(
+ conversation,
+ AvatarService.getSystemUiAvatarSize(mXmppConnectionService)));
+ notificationBuilder.setContentTitle(getConversationName(conversation));
+ if (Config.HIDE_MESSAGE_TEXT_IN_NOTIFICATION) {
+ int count = messages.size();
+ notificationBuilder.setContentText(
mXmppConnectionService
- .getAvatarService()
- .get(
- conversation,
- AvatarService.getSystemUiAvatarSize(mXmppConnectionService)));
- mBuilder.setContentTitle(getConversationName(conversation));
- if (Config.HIDE_MESSAGE_TEXT_IN_NOTIFICATION) {
- int count = messages.size();
- mBuilder.setContentText(
- mXmppConnectionService
- .getResources()
- .getQuantityString(R.plurals.x_messages, count, count));
+ .getResources()
+ .getQuantityString(R.plurals.x_messages, count, count));
+ } else {
+ final Message message;
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P
+ && (message = getImage(messages)) != null) {
+ modifyForImage(notificationBuilder, message, messages);
} else {
- Message message;
- // TODO starting with Android 9 we might want to put images in MessageStyle
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P
- && (message = getImage(messages)) != null) {
- modifyForImage(mBuilder, message, messages);
- } else {
- modifyForTextOnly(mBuilder, messages);
- }
- RemoteInput remoteInput =
- new RemoteInput.Builder("text_reply")
- .setLabel(
- UIHelper.getMessageHint(
- mXmppConnectionService, conversation))
- .build();
- PendingIntent markAsReadPendingIntent = createReadPendingIntent(conversation);
- NotificationCompat.Action markReadAction =
- new NotificationCompat.Action.Builder(
- R.drawable.ic_drafts_white_24dp,
- mXmppConnectionService.getString(R.string.mark_as_read),
- markAsReadPendingIntent)
- .setSemanticAction(
- NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ)
- .setShowsUserInterface(false)
- .build();
- final String replyLabel = mXmppConnectionService.getString(R.string.reply);
- final String lastMessageUuid = Iterables.getLast(messages).getUuid();
- final NotificationCompat.Action replyAction =
- new NotificationCompat.Action.Builder(
- R.drawable.ic_send_text_offline,
- replyLabel,
- createReplyIntent(conversation, lastMessageUuid, false))
- .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
- .setShowsUserInterface(false)
- .addRemoteInput(remoteInput)
- .build();
- final NotificationCompat.Action wearReplyAction =
- new NotificationCompat.Action.Builder(
- R.drawable.ic_wear_reply,
- replyLabel,
- createReplyIntent(conversation, lastMessageUuid, true))
- .addRemoteInput(remoteInput)
- .build();
- mBuilder.extend(
- new NotificationCompat.WearableExtender().addAction(wearReplyAction));
- int addedActionsCount = 1;
- mBuilder.addAction(markReadAction);
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- mBuilder.addAction(replyAction);
- ++addedActionsCount;
- }
+ modifyForTextOnly(notificationBuilder, messages);
+ }
+ RemoteInput remoteInput =
+ new RemoteInput.Builder("text_reply")
+ .setLabel(UIHelper.getMessageHint(mXmppConnectionService, conversation))
+ .build();
+ PendingIntent markAsReadPendingIntent = createReadPendingIntent(conversation);
+ NotificationCompat.Action markReadAction =
+ new NotificationCompat.Action.Builder(
+ R.drawable.ic_mark_chat_read_24dp,
+ mXmppConnectionService.getString(R.string.mark_as_read),
+ markAsReadPendingIntent)
+ .setSemanticAction(
+ NotificationCompat.Action.SEMANTIC_ACTION_MARK_AS_READ)
+ .setShowsUserInterface(false)
+ .build();
+ final String replyLabel = mXmppConnectionService.getString(R.string.reply);
+ final String lastMessageUuid = Iterables.getLast(messages).getUuid();
+ final NotificationCompat.Action replyAction =
+ new NotificationCompat.Action.Builder(
+ R.drawable.ic_send_24dp,
+ replyLabel,
+ createReplyIntent(conversation, lastMessageUuid, false))
+ .setSemanticAction(NotificationCompat.Action.SEMANTIC_ACTION_REPLY)
+ .setShowsUserInterface(false)
+ .addRemoteInput(remoteInput)
+ .build();
+ final NotificationCompat.Action wearReplyAction =
+ new NotificationCompat.Action.Builder(
+ R.drawable.ic_reply_24dp,
+ replyLabel,
+ createReplyIntent(conversation, lastMessageUuid, true))
+ .addRemoteInput(remoteInput)
+ .build();
+ notificationBuilder.extend(
+ new NotificationCompat.WearableExtender().addAction(wearReplyAction));
+ int addedActionsCount = 1;
+ notificationBuilder.addAction(markReadAction);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ notificationBuilder.addAction(replyAction);
+ ++addedActionsCount;
+ }
- if (displaySnoozeAction(messages)) {
- String label = mXmppConnectionService.getString(R.string.snooze);
- PendingIntent pendingSnoozeIntent = createSnoozeIntent(conversation);
- NotificationCompat.Action snoozeAction =
- new NotificationCompat.Action.Builder(
- R.drawable.ic_notifications_paused_white_24dp,
- label,
- pendingSnoozeIntent)
- .build();
- mBuilder.addAction(snoozeAction);
- ++addedActionsCount;
- }
- if (addedActionsCount < 3) {
- final Message firstLocationMessage = getFirstLocationMessage(messages);
- if (firstLocationMessage != null) {
- final PendingIntent pendingShowLocationIntent =
- createShowLocationIntent(firstLocationMessage);
- if (pendingShowLocationIntent != null) {
- final String label =
- mXmppConnectionService
- .getResources()
- .getString(R.string.show_location);
- NotificationCompat.Action locationAction =
- new NotificationCompat.Action.Builder(
- R.drawable.ic_room_white_24dp,
- label,
- pendingShowLocationIntent)
- .build();
- mBuilder.addAction(locationAction);
- ++addedActionsCount;
- }
- }
- }
- if (addedActionsCount < 3) {
- Message firstDownloadableMessage = getFirstDownloadableMessage(messages);
- if (firstDownloadableMessage != null) {
- String label =
+ if (displaySnoozeAction(messages)) {
+ String label = mXmppConnectionService.getString(R.string.snooze);
+ PendingIntent pendingSnoozeIntent = createSnoozeIntent(conversation);
+ NotificationCompat.Action snoozeAction =
+ new NotificationCompat.Action.Builder(
+ R.drawable.ic_notifications_paused_24dp,
+ label,
+ pendingSnoozeIntent)
+ .build();
+ notificationBuilder.addAction(snoozeAction);
+ ++addedActionsCount;
+ }
+ if (addedActionsCount < 3) {
+ final Message firstLocationMessage = getFirstLocationMessage(messages);
+ if (firstLocationMessage != null) {
+ final PendingIntent pendingShowLocationIntent =
+ createShowLocationIntent(firstLocationMessage);
+ if (pendingShowLocationIntent != null) {
+ final String label =
mXmppConnectionService
.getResources()
- .getString(
- R.string.download_x_file,
- UIHelper.getFileDescriptionString(
- mXmppConnectionService,
- firstDownloadableMessage));
- PendingIntent pendingDownloadIntent =
- createDownloadIntent(firstDownloadableMessage);
- NotificationCompat.Action downloadAction =
+ .getString(R.string.show_location);
+ NotificationCompat.Action locationAction =
new NotificationCompat.Action.Builder(
- R.drawable.ic_file_download_white_24dp,
- label,
- pendingDownloadIntent)
+ R.drawable.ic_location_pin_24dp,
+ label,
+ pendingShowLocationIntent)
.build();
- mBuilder.addAction(downloadAction);
+ notificationBuilder.addAction(locationAction);
++addedActionsCount;
}
}
}
- final ShortcutInfoCompat info;
- if (conversation.getMode() == Conversation.MODE_SINGLE) {
- final Contact contact = conversation.getContact();
- final Uri systemAccount = contact.getSystemAccount();
- if (systemAccount != null) {
- mBuilder.addPerson(systemAccount.toString());
+ if (addedActionsCount < 3) {
+ Message firstDownloadableMessage = getFirstDownloadableMessage(messages);
+ if (firstDownloadableMessage != null) {
+ String label =
+ mXmppConnectionService
+ .getResources()
+ .getString(
+ R.string.download_x_file,
+ UIHelper.getFileDescriptionString(
+ mXmppConnectionService,
+ firstDownloadableMessage));
+ PendingIntent pendingDownloadIntent =
+ createDownloadIntent(firstDownloadableMessage);
+ NotificationCompat.Action downloadAction =
+ new NotificationCompat.Action.Builder(
+ R.drawable.ic_download_24dp,
+ label,
+ pendingDownloadIntent)
+ .build();
+ notificationBuilder.addAction(downloadAction);
+ ++addedActionsCount;
}
- info = mXmppConnectionService.getShortcutService().getShortcutInfoCompat(contact);
- } else {
- info =
- mXmppConnectionService
- .getShortcutService()
- .getShortcutInfoCompat(conversation.getMucOptions(), conversation.getNextCounterpart());
}
- mBuilder.setWhen(conversation.getLatestMessage().getTimeSent());
- mBuilder.setSmallIcon(R.drawable.ic_notification);
- mBuilder.setDeleteIntent(createDeleteIntent(conversation));
- mBuilder.setContentIntent(createContentIntent(conversation));
- mBuilder.setShortcutInfo(info);
+ }
+ final ShortcutInfoCompat info;
+ if (conversation.getMode() == Conversation.MODE_SINGLE) {
+ final Contact contact = conversation.getContact();
+ final Uri systemAccount = contact.getSystemAccount();
+ if (systemAccount != null) {
+ notificationBuilder.addPerson(systemAccount.toString());
+ }
+ info = mXmppConnectionService.getShortcutService().getShortcutInfoCompat(contact);
+ } else {
+ info =
+ mXmppConnectionService
+ .getShortcutService()
+ .getShortcutInfoCompat(conversation.getMucOptions());
+ }
+ notificationBuilder.setWhen(conversation.getLatestMessage().getTimeSent());
+ notificationBuilder.setSmallIcon(R.drawable.ic_app_icon_notification);
+ notificationBuilder.setDeleteIntent(createDeleteIntent(conversation));
+ notificationBuilder.setContentIntent(createContentIntent(conversation));
+ if (channel.equals(MESSAGES_NOTIFICATION_CHANNEL)) {
+ // when do not want 'customized' notifications for silent notifications in their
+ // respective channels
+ notificationBuilder.setShortcutInfo(info);
if (Build.VERSION.SDK_INT >= 30) {
mXmppConnectionService
.getSystemService(ShortcutManager.class)
.pushDynamicShortcut(info.toShortcutInfo());
}
}
- return mBuilder;
+ return notificationBuilder;
}
private void modifyForImage(
@@ -1666,21 +1701,34 @@ public class NotificationService {
}
private PendingIntent createCallAction(String sessionId, final String action, int requestCode) {
- return pendingServiceIntent(mXmppConnectionService, action, requestCode, ImmutableMap.of(RtpSessionActivity.EXTRA_SESSION_ID, sessionId));
+ return pendingServiceIntent(
+ mXmppConnectionService,
+ action,
+ requestCode,
+ ImmutableMap.of(RtpSessionActivity.EXTRA_SESSION_ID, sessionId));
}
private PendingIntent createSnoozeIntent(final Conversation conversation) {
- return pendingServiceIntent(mXmppConnectionService, XmppConnectionService.ACTION_SNOOZE, generateRequestCode(conversation,22),ImmutableMap.of("uuid",conversation.getUuid()));
+ return pendingServiceIntent(
+ mXmppConnectionService,
+ XmppConnectionService.ACTION_SNOOZE,
+ generateRequestCode(conversation, 22),
+ ImmutableMap.of("uuid", conversation.getUuid()));
}
- private static PendingIntent pendingServiceIntent(final Context context, final String action, final int requestCode) {
+ private static PendingIntent pendingServiceIntent(
+ final Context context, final String action, final int requestCode) {
return pendingServiceIntent(context, action, requestCode, ImmutableMap.of());
}
- private static PendingIntent pendingServiceIntent(final Context context, final String action, final int requestCode, final Map extras) {
+ private static PendingIntent pendingServiceIntent(
+ final Context context,
+ final String action,
+ final int requestCode,
+ final Map extras) {
final Intent intent = new Intent(context, XmppConnectionService.class);
intent.setAction(action);
- for(final Map.Entry entry : extras.entrySet()) {
+ for (final Map.Entry entry : extras.entrySet()) {
intent.putExtra(entry.getKey(), entry.getValue());
}
return PendingIntent.getService(
@@ -1693,8 +1741,7 @@ public class NotificationService {
}
private boolean wasHighlightedOrPrivate(final Message message) {
- if (message.getConversation() instanceof Conversation) {
- Conversation conversation = (Conversation) message.getConversation();
+ if (message.getConversation() instanceof Conversation conversation) {
final String nick = conversation.getMucOptions().getActualNick();
final Pattern highlight = generateNickHighlightPattern(nick);
if (message.getBody() == null || nick == null) {
@@ -1743,8 +1790,7 @@ public class NotificationService {
connected = 0;
} else {
enabled = Iterables.size(Iterables.filter(accounts, Account::isEnabled));
- connected =
- Iterables.size(Iterables.filter(accounts, Account::isOnlineAndConnected));
+ connected = Iterables.size(Iterables.filter(accounts, Account::isOnlineAndConnected));
}
mBuilder.setContentText(
mXmppConnectionService.getString(R.string.connected_accounts, connected, enabled));
@@ -1754,10 +1800,7 @@ public class NotificationService {
}
mBuilder.setWhen(0)
.setPriority(Notification.PRIORITY_MIN)
- .setSmallIcon(
- connected > 0
- ? R.drawable.ic_link_white_24dp
- : R.drawable.ic_link_off_white_24dp)
+ .setSmallIcon(connected > 0 ? R.drawable.ic_link_24dp : R.drawable.ic_link_off_24dp)
.setLocalOnly(true);
if (Compatibility.runsTwentySix()) {
@@ -1824,10 +1867,17 @@ public class NotificationService {
}
}
if (mXmppConnectionService.foregroundNotificationNeedsUpdatingWhenErrorStateChanges()) {
- notify(FOREGROUND_NOTIFICATION_ID, createForegroundNotification());
+ try {
+ notify(FOREGROUND_NOTIFICATION_ID, createForegroundNotification());
+ } catch (final RuntimeException e) {
+ Log.d(
+ Config.LOGTAG,
+ "not refreshing foreground service notification because service has died",
+ e);
+ }
}
final Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService);
- if (errors.size() == 0) {
+ if (errors.isEmpty()) {
cancel(ERROR_NOTIFICATION_ID);
return;
} else if (errors.size() == 1) {
@@ -1839,14 +1889,27 @@ public class NotificationService {
mXmppConnectionService.getString(R.string.problem_connecting_to_accounts));
mBuilder.setContentText(mXmppConnectionService.getString(R.string.touch_to_fix));
}
- mBuilder.addAction(
- R.drawable.ic_autorenew_white_24dp,
- mXmppConnectionService.getString(R.string.try_again),
- pendingServiceIntent(mXmppConnectionService, XmppConnectionService.ACTION_TRY_AGAIN, 45));
+ try {
+ mBuilder.addAction(
+ R.drawable.ic_autorenew_24dp,
+ mXmppConnectionService.getString(R.string.try_again),
+ pendingServiceIntent(
+ mXmppConnectionService, XmppConnectionService.ACTION_TRY_AGAIN, 45));
+ mBuilder.setDeleteIntent(
+ pendingServiceIntent(
+ mXmppConnectionService,
+ XmppConnectionService.ACTION_DISMISS_ERROR_NOTIFICATIONS,
+ 69));
+ } catch (final RuntimeException e) {
+ Log.d(
+ Config.LOGTAG,
+ "not including some actions in error notification because service has died",
+ e);
+ }
if (torNotAvailable) {
if (TorServiceUtils.isOrbotInstalled(mXmppConnectionService)) {
mBuilder.addAction(
- R.drawable.ic_play_circle_filled_white_48dp,
+ R.drawable.ic_play_circle_24dp,
mXmppConnectionService.getString(R.string.start_orbot),
PendingIntent.getActivity(
mXmppConnectionService,
@@ -1858,7 +1921,7 @@ public class NotificationService {
: PendingIntent.FLAG_UPDATE_CURRENT));
} else {
mBuilder.addAction(
- R.drawable.ic_file_download_white_24dp,
+ R.drawable.ic_download_24dp,
mXmppConnectionService.getString(R.string.install_orbot),
PendingIntent.getActivity(
mXmppConnectionService,
@@ -1870,7 +1933,6 @@ public class NotificationService {
: PendingIntent.FLAG_UPDATE_CURRENT));
}
}
- mBuilder.setDeleteIntent(pendingServiceIntent(mXmppConnectionService,XmppConnectionService.ACTION_DISMISS_ERROR_NOTIFICATIONS, 69));
mBuilder.setVisibility(Notification.VISIBILITY_PRIVATE);
mBuilder.setSmallIcon(R.drawable.ic_warning_white_24dp);
mBuilder.setLocalOnly(true);
@@ -1897,41 +1959,66 @@ public class NotificationService {
notify(ERROR_NOTIFICATION_ID, mBuilder.build());
}
- void updateFileAddingNotification(int current, Message message) {
- Notification.Builder mBuilder = new Notification.Builder(mXmppConnectionService);
- mBuilder.setContentTitle(mXmppConnectionService.getString(R.string.transcoding_video));
- mBuilder.setProgress(100, current, false);
- mBuilder.setSmallIcon(R.drawable.ic_hourglass_empty_white_24dp);
- mBuilder.setContentIntent(createContentIntent(message.getConversation()));
- mBuilder.setOngoing(true);
- if (Compatibility.runsTwentySix()) {
- mBuilder.setChannelId("compression");
- }
- Notification notification = mBuilder.build();
- notify(FOREGROUND_NOTIFICATION_ID, notification);
+ void updateFileAddingNotification(final int current, final Message message) {
+
+ final Notification notification = videoTranscoding(current, message);
+ notify(ONGOING_VIDEO_TRANSCODING_NOTIFICATION_ID, notification);
}
- private void notify(String tag, int id, Notification notification) {
- final NotificationManagerCompat notificationManager =
- NotificationManagerCompat.from(mXmppConnectionService);
+ private Notification videoTranscoding(final int current, @Nullable final Message message) {
+ final Notification.Builder builder = new Notification.Builder(mXmppConnectionService);
+ builder.setContentTitle(mXmppConnectionService.getString(R.string.transcoding_video));
+ if (current >= 0) {
+ builder.setProgress(100, current, false);
+ } else {
+ builder.setProgress(100, 0, true);
+ }
+ builder.setSmallIcon(R.drawable.ic_hourglass_top_24dp);
+ if (message != null) {
+ builder.setContentIntent(createContentIntent(message.getConversation()));
+ }
+ builder.setOngoing(true);
+ if (Compatibility.runsTwentySix()) {
+ builder.setChannelId("compression");
+ }
+ return builder.build();
+ }
+
+ public Notification getIndeterminateVideoTranscoding() {
+ return videoTranscoding(-1, null);
+ }
+
+ private void notify(final String tag, final int id, final Notification notification) {
+ if (ActivityCompat.checkSelfPermission(
+ mXmppConnectionService, Manifest.permission.POST_NOTIFICATIONS)
+ != PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ final var notificationManager =
+ mXmppConnectionService.getSystemService(NotificationManager.class);
try {
notificationManager.notify(tag, id, notification);
- } catch (RuntimeException e) {
+ } catch (final RuntimeException e) {
Log.d(Config.LOGTAG, "unable to make notification", e);
}
}
- public void notify(int id, Notification notification) {
- final NotificationManagerCompat notificationManager =
- NotificationManagerCompat.from(mXmppConnectionService);
+ public void notify(final int id, final Notification notification) {
+ if (ActivityCompat.checkSelfPermission(
+ mXmppConnectionService, Manifest.permission.POST_NOTIFICATIONS)
+ != PackageManager.PERMISSION_GRANTED) {
+ return;
+ }
+ final var notificationManager =
+ mXmppConnectionService.getSystemService(NotificationManager.class);
try {
notificationManager.notify(id, notification);
- } catch (RuntimeException e) {
+ } catch (final RuntimeException e) {
Log.d(Config.LOGTAG, "unable to make notification", e);
}
}
- public void cancel(int id) {
+ public void cancel(final int id) {
final NotificationManagerCompat notificationManager =
NotificationManagerCompat.from(mXmppConnectionService);
try {
@@ -1986,14 +2073,4 @@ public class NotificationService {
return lastTime;
}
}
-
- private class VibrationRunnable implements Runnable {
-
- @Override
- public void run() {
- final Vibrator vibrator =
- (Vibrator) mXmppConnectionService.getSystemService(Context.VIBRATOR_SERVICE);
- vibrator.vibrate(CALL_PATTERN, -1);
- }
- }
}
diff --git a/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java b/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java
index d152c5d07..4aab05cee 100644
--- a/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java
+++ b/src/main/java/eu/siacs/conversations/services/UnifiedPushBroker.java
@@ -12,6 +12,7 @@ import android.preference.PreferenceManager;
import android.util.Log;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
@@ -27,6 +28,7 @@ import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.parser.AbstractParser;
import eu.siacs.conversations.persistance.UnifiedPushDatabase;
+import eu.siacs.conversations.receiver.UnifiedPushDistributor;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.Jid;
@@ -85,24 +87,24 @@ public class UnifiedPushBroker {
service.sendPresencePacket(account, presence);
}
- public Optional renewUnifiedPushEndpoints() {
- return renewUnifiedPushEndpoints(null);
+ public void renewUnifiedPushEndpoints() {
+ renewUnifiedPushEndpoints(null);
}
- public Optional renewUnifiedPushEndpoints(final PushTargetMessenger pushTargetMessenger) {
+ public Optional renewUnifiedPushEndpoints(@Nullable final PushTargetMessenger pushTargetMessenger) {
final Optional transportOptional = getTransport();
if (transportOptional.isPresent()) {
final Transport transport = transportOptional.get();
if (transport.account.isEnabled()) {
renewUnifiedEndpoint(transportOptional.get(), pushTargetMessenger);
} else {
- if (pushTargetMessenger.messenger != null) {
+ if (pushTargetMessenger != null && pushTargetMessenger.messenger != null) {
sendRegistrationDelayed(pushTargetMessenger.messenger,"account is disabled");
}
Log.d(Config.LOGTAG, "skipping UnifiedPush endpoint renewal. Account is disabled");
}
} else {
- if (pushTargetMessenger.messenger != null) {
+ if (pushTargetMessenger != null && pushTargetMessenger.messenger != null) {
sendRegistrationDelayed(pushTargetMessenger.messenger,"no transport selected");
}
Log.d(Config.LOGTAG, "skipping UnifiedPush endpoint renewal. No transport selected");
@@ -403,7 +405,12 @@ public class UnifiedPushBroker {
updateIntent.putExtra("token", target.instance);
updateIntent.putExtra("bytesMessage", payload);
updateIntent.putExtra("message", new String(payload, StandardCharsets.UTF_8));
- // TODO add distributor verification?
+ final var distributorVerificationIntent = new Intent();
+ distributorVerificationIntent.setPackage(service.getPackageName());
+ final var pendingIntent =
+ PendingIntent.getBroadcast(
+ service, 0, distributorVerificationIntent, PendingIntent.FLAG_IMMUTABLE);
+ updateIntent.putExtra("distributor", pendingIntent);
service.sendBroadcast(updateIntent);
}
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
index f66e29a96..9492645f5 100644
--- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
@@ -42,8 +42,6 @@ import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.provider.ContactsContract;
import android.security.KeyChain;
-import android.telephony.PhoneStateListener;
-import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
@@ -53,6 +51,7 @@ import android.util.Pair;
import androidx.annotation.BoolRes;
import androidx.annotation.IntegerRes;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.core.app.RemoteInput;
import androidx.core.content.ContextCompat;
import androidx.core.util.Consumer;
@@ -60,6 +59,8 @@ import androidx.core.util.Consumer;
import com.google.common.base.Objects;
import com.google.common.base.Optional;
import com.google.common.base.Strings;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.Iterables;
import org.conscrypt.Conscrypt;
import org.jxmpp.stringprep.libidn.LibIdnXmppStringprep;
@@ -88,12 +89,14 @@ import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
+import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
+import eu.siacs.conversations.AppSettings;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.android.JabberIdContact;
@@ -128,10 +131,10 @@ import eu.siacs.conversations.parser.PresenceParser;
import eu.siacs.conversations.persistance.DatabaseBackend;
import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.persistance.UnifiedPushDatabase;
+import eu.siacs.conversations.receiver.SystemEventReceiver;
import eu.siacs.conversations.ui.ChooseAccountForProfilePictureActivity;
import eu.siacs.conversations.ui.ConversationsActivity;
import eu.siacs.conversations.ui.RtpSessionActivity;
-import eu.siacs.conversations.ui.SettingsActivity;
import eu.siacs.conversations.ui.UiCallback;
import eu.siacs.conversations.ui.interfaces.OnAvatarPublication;
import eu.siacs.conversations.ui.interfaces.OnMediaLoaded;
@@ -157,6 +160,7 @@ import eu.siacs.conversations.utils.XmppUri;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.LocalizedContent;
import eu.siacs.conversations.xml.Namespace;
+import eu.siacs.conversations.xmpp.InvalidJid;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.OnBindListener;
import eu.siacs.conversations.xmpp.OnContactStatusChanged;
@@ -203,6 +207,7 @@ public class XmppConnectionService extends Service {
public static final String ACTION_DISMISS_CALL = "dismiss_call";
public static final String ACTION_END_CALL = "end_call";
public static final String ACTION_PROVISION_ACCOUNT = "provision_account";
+ public static final String ACTION_CALL_INTEGRATION_SERVICE_STARTED = "call_integration_service_started";
private static final String ACTION_POST_CONNECTIVITY_CHANGE = "eu.siacs.conversations.POST_CONNECTIVITY_CHANGE";
public static final String ACTION_RENEW_UNIFIED_PUSH_ENDPOINTS = "eu.siacs.conversations.UNIFIED_PUSH_RENEW";
public static final String ACTION_QUICK_LOG = "eu.siacs.conversations.QUICK_LOG";
@@ -244,7 +249,7 @@ public class XmppConnectionService extends Service {
private final ChannelDiscoveryService mChannelDiscoveryService = new ChannelDiscoveryService(this);
private final ShortcutService mShortcutService = new ShortcutService(this);
private final AtomicBoolean mInitialAddressbookSyncCompleted = new AtomicBoolean(false);
- private final AtomicBoolean mForceForegroundService = new AtomicBoolean(false);
+ private final AtomicBoolean mOngoingVideoTranscoding = new AtomicBoolean(false);
private final AtomicBoolean mForceDuringOnCreate = new AtomicBoolean(false);
private final AtomicReference ongoingCall = new AtomicReference<>();
private final OnMessagePacketReceived mMessageParser = new MessageParser(this);
@@ -308,16 +313,6 @@ public class XmppConnectionService extends Service {
return false;
}
};
- private final AtomicBoolean isPhoneInCall = new AtomicBoolean(false);
- private final PhoneStateListener phoneStateListener = new PhoneStateListener() {
- @Override
- public void onCallStateChanged(final int state, final String phoneNumber) {
- isPhoneInCall.set(state != TelephonyManager.CALL_STATE_IDLE);
- if (state == TelephonyManager.CALL_STATE_OFFHOOK) {
- mJingleConnectionManager.notifyPhoneCallStarted();
- }
- }
- };
private boolean destroyed = false;
@@ -383,16 +378,27 @@ public class XmppConnectionService extends Service {
} else if (!account.getXmppConnection().getFeatures().bookmarksConversion()) {
fetchBookmarks(account);
}
+
+ if (connection.getFeatures().mds()) {
+ fetchMessageDisplayedSynchronization(account);
+ } else {
+ Log.d(Config.LOGTAG,account.getJid()+": server has no support for mds");
+ }
final boolean flexible = account.getXmppConnection().getFeatures().flexibleOfflineMessageRetrieval();
final boolean catchup = getMessageArchiveService().inCatchup(account);
+ final boolean trackOfflineMessageRetrieval;
if (flexible && catchup && account.getXmppConnection().isMamPreferenceAlways()) {
+ trackOfflineMessageRetrieval = false;
sendIqPacket(account, mIqGenerator.purgeOfflineMessages(), (acc, packet) -> {
if (packet.getType() == IqPacket.TYPE.RESULT) {
Log.d(Config.LOGTAG, acc.getJid().asBareJid() + ": successfully purged offline messages");
}
});
+ } else {
+ trackOfflineMessageRetrieval = true;
}
sendPresence(account);
+ account.getXmppConnection().trackOfflineMessageRetrieval(trackOfflineMessageRetrieval);
if (mPushManagementService.available(account)) {
mPushManagementService.registerPushTokenOnServer(account);
}
@@ -402,6 +408,7 @@ public class XmppConnectionService extends Service {
unifiedPushBroker.renewUnifiedPushEndpointsOnBind(account);
}
};
+
private final AtomicLong mLastExpiryRun = new AtomicLong(0);
private final LruCache, ServiceDiscoveryResult> discoCache = new LruCache<>(20);
private final OnStatusChanged statusListener = new OnStatusChanged() {
@@ -526,13 +533,13 @@ public class XmppConnectionService extends Service {
}
}
- public void startForcingForegroundNotification() {
- mForceForegroundService.set(true);
+ public void startOngoingVideoTranscodingForegroundNotification() {
+ mOngoingVideoTranscoding.set(true);
toggleForegroundService();
}
- public void stopForcingForegroundNotification() {
- mForceForegroundService.set(false);
+ public void stopOngoingVideoTranscodingForegroundNotification() {
+ mOngoingVideoTranscoding.set(false);
toggleForegroundService();
}
@@ -699,7 +706,7 @@ public class XmppConnectionService extends Service {
@Override
public int onStartCommand(final Intent intent, int flags, int startId) {
final String action = Strings.nullToEmpty(intent == null ? null : intent.getAction());
- final boolean needsForegroundService = intent != null && intent.getBooleanExtra(EventReceiver.EXTRA_NEEDS_FOREGROUND_SERVICE, false);
+ final boolean needsForegroundService = intent != null && intent.getBooleanExtra(SystemEventReceiver.EXTRA_NEEDS_FOREGROUND_SERVICE, false);
if (needsForegroundService) {
Log.d(Config.LOGTAG, "toggle forced foreground service after receiving event (action=" + action + ")");
toggleForegroundService(true);
@@ -886,9 +893,7 @@ public class XmppConnectionService extends Service {
}
break;
case ACTION_IDLE_PING:
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- scheduleNextIdlePing();
- }
+ scheduleNextIdlePing();
break;
case ACTION_FCM_MESSAGE_RECEIVED:
Log.d(Config.LOGTAG, "push message arrived in service. account");
@@ -912,7 +917,12 @@ public class XmppConnectionService extends Service {
}
return START_NOT_STICKY;
}
- manageAccountConnectionStates(action, intent == null ? null : intent.getExtras());
+ final var extras = intent == null ? null : intent.getExtras();
+ try {
+ internalPingExecutor.execute(() -> manageAccountConnectionStates(action, extras));
+ } catch (final RejectedExecutionException e) {
+ Log.e(Config.LOGTAG, "can not schedule connection states manager");
+ }
if (SystemClock.elapsedRealtime() - mLastExpiryRun.get() >= Config.EXPIRY_INTERVAL) {
expireOldMessages();
}
@@ -940,27 +950,45 @@ public class XmppConnectionService extends Service {
manageAccountConnectionStates(ACTION_INTERNAL_PING, null);
}
- private synchronized void manageAccountConnectionStates(final String action, final Bundle extras) {
+ private synchronized void manageAccountConnectionStates(
+ final String action, final Bundle extras) {
final String pushedAccountHash = extras == null ? null : extras.getString("account");
- final boolean interactive = Arrays.asList(ACTION_TRY_AGAIN).contains(action);
+ final boolean interactive = java.util.Objects.equals(ACTION_TRY_AGAIN, action);
WakeLockHelper.acquire(wakeLock);
- boolean pingNow = ConnectivityManager.CONNECTIVITY_ACTION.equals(action) || (Config.POST_CONNECTIVITY_CHANGE_PING_INTERVAL > 0 && ACTION_POST_CONNECTIVITY_CHANGE.equals(action));
+ boolean pingNow =
+ ConnectivityManager.CONNECTIVITY_ACTION.equals(action)
+ || (Config.POST_CONNECTIVITY_CHANGE_PING_INTERVAL > 0
+ && ACTION_POST_CONNECTIVITY_CHANGE.equals(action));
final HashSet pingCandidates = new HashSet<>();
- final String androidId = PhoneHelper.getAndroidId(this);
+ final String androidId = pushedAccountHash == null ? null : PhoneHelper.getAndroidId(this);
for (final Account account : accounts) {
- final boolean pushWasMeantForThisAccount = CryptoHelper.getAccountFingerprint(account, androidId).equals(pushedAccountHash);
- pingNow |= processAccountState(account,
- interactive,
- "ui".equals(action),
- pushWasMeantForThisAccount,
- pingCandidates);
+ final boolean pushWasMeantForThisAccount =
+ androidId != null
+ && CryptoHelper.getAccountFingerprint(account, androidId)
+ .equals(pushedAccountHash);
+ pingNow |=
+ processAccountState(
+ account,
+ interactive,
+ "ui".equals(action),
+ pushWasMeantForThisAccount,
+ pingCandidates);
}
if (pingNow) {
- for (Account account : pingCandidates) {
+ for (final Account account : pingCandidates) {
final boolean lowTimeout = isInLowPingTimeoutMode(account);
account.getXmppConnection().sendPing();
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + " send ping (action=" + action + ",lowTimeout=" + lowTimeout + ")");
- scheduleWakeUpCall(lowTimeout ? Config.LOW_PING_TIMEOUT : Config.PING_TIMEOUT, account.getUuid().hashCode());
+ Log.d(
+ Config.LOGTAG,
+ account.getJid().asBareJid()
+ + " send ping (action="
+ + action
+ + ",lowTimeout="
+ + lowTimeout
+ + ")");
+ scheduleWakeUpCall(
+ lowTimeout ? Config.LOW_PING_TIMEOUT : Config.PING_TIMEOUT,
+ account.getUuid().hashCode());
}
}
WakeLockHelper.release(wakeLock);
@@ -974,7 +1002,7 @@ public class XmppConnectionService extends Service {
}
}
- private boolean processAccountState(Account account, boolean interactive, boolean isUiAction, boolean isAccountPushed, HashSet pingCandidates) {
+ private boolean processAccountState(final Account account, final boolean interactive, final boolean isUiAction, final boolean isAccountPushed, final HashSet pingCandidates) {
if (!account.getStatus().isAttemptReconnect()) {
return false;
}
@@ -1069,15 +1097,10 @@ public class XmppConnectionService extends Service {
}
public boolean isDataSaverDisabled() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- final ConnectivityManager connectivityManager =
- (ConnectivityManager) getSystemService(CONNECTIVITY_SERVICE);
- return !connectivityManager.isActiveNetworkMetered()
- || Compatibility.getRestrictBackgroundStatus(connectivityManager)
- == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
- } else {
- return true;
- }
+ final ConnectivityManager connectivityManager = getSystemService(ConnectivityManager.class);
+ return !Compatibility.isActiveNetworkMetered(connectivityManager)
+ || Compatibility.getRestrictBackgroundStatus(connectivityManager)
+ == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_DISABLED;
}
private void directReply(final Conversation conversation, final String body, final String lastMessageUuid, final boolean dismissAfterReply) {final Message inReplyTo = lastMessageUuid == null ? null : conversation.findMessageWithUuid(lastMessageUuid);
@@ -1158,23 +1181,23 @@ public class XmppConnectionService extends Service {
}
public boolean isScreenLocked() {
- final KeyguardManager keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
- final PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
+ final KeyguardManager keyguardManager = getSystemService(KeyguardManager.class);
+ final PowerManager powerManager = getSystemService(PowerManager.class);
final boolean locked = keyguardManager != null && keyguardManager.isKeyguardLocked();
- final boolean interactive = powerManager != null && powerManager.isInteractive();
+ final boolean interactive;
+ try {
+ interactive = powerManager != null && powerManager.isInteractive();
+ } catch (final Exception e) {
+ return false;
+ }
return locked || !interactive;
}
private boolean isPhoneSilenced() {
- final boolean notificationDnd;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- final NotificationManager notificationManager = getSystemService(NotificationManager.class);
- final int filter = notificationManager == null ? NotificationManager.INTERRUPTION_FILTER_UNKNOWN : notificationManager.getCurrentInterruptionFilter();
- notificationDnd = filter >= NotificationManager.INTERRUPTION_FILTER_PRIORITY;
- } else {
- notificationDnd = false;
- }
- final AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+ final NotificationManager notificationManager = getSystemService(NotificationManager.class);
+ final int filter = notificationManager == null ? NotificationManager.INTERRUPTION_FILTER_UNKNOWN : notificationManager.getCurrentInterruptionFilter();
+ final boolean notificationDnd = filter >= NotificationManager.INTERRUPTION_FILTER_PRIORITY;
+ final AudioManager audioManager = getSystemService(AudioManager.class);
final int ringerMode = audioManager == null ? AudioManager.RINGER_MODE_NORMAL : audioManager.getRingerMode();
try {
if (treatVibrateAsSilent()) {
@@ -1182,7 +1205,7 @@ public class XmppConnectionService extends Service {
} else {
return notificationDnd || ringerMode == AudioManager.RINGER_MODE_SILENT;
}
- } catch (Throwable throwable) {
+ } catch (final Throwable throwable) {
Log.d(Config.LOGTAG, "platform bug in isPhoneSilenced (" + throwable.getMessage() + ")");
return notificationDnd;
}
@@ -1278,7 +1301,7 @@ public class XmppConnectionService extends Service {
Log.e(Config.LOGTAG, "unable to initialize security provider", throwable);
}
Resolver.init(this);
- updateMemorizingTrustmanager();
+ updateMemorizingTrustManager();
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
final int cacheSize = maxMemory / 8;
this.mBitmapCache = new LruCache(cacheSize) {
@@ -1311,19 +1334,21 @@ public class XmppConnectionService extends Service {
Log.d(Config.LOGTAG, "restoring accounts...");
this.accounts = databaseBackend.getAccounts();
final SharedPreferences.Editor editor = getPreferences().edit();
- if (this.accounts.size() == 0 && Arrays.asList("Sony", "Sony Ericsson").contains(Build.MANUFACTURER)) {
- editor.putBoolean(SettingsActivity.KEEP_FOREGROUND_SERVICE, true);
- Log.d(Config.LOGTAG, Build.MANUFACTURER + " is on blacklist. enabling foreground service");
- }
final boolean hasEnabledAccounts = hasEnabledAccounts();
- editor.putBoolean(EventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts).apply();
+ editor.putBoolean(SystemEventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts).apply();
editor.apply();
toggleSetProfilePictureActivity(hasEnabledAccounts);
reconfigurePushDistributor();
+ if (CallIntegration.hasSystemFeature(this)) {
+ CallIntegrationConnectionService.togglePhoneAccountsAsync(this, this.accounts);
+ }
+
restoreFromDatabase();
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
+ if (QuickConversationsService.isContactListIntegration(this)
+ && ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)
+ == PackageManager.PERMISSION_GRANTED) {
startContactObserver();
}
FILE_OBSERVER_EXECUTOR.execute(fileBackend::deleteHistoricAvatarPath);
@@ -1352,20 +1377,18 @@ public class XmppConnectionService extends Service {
this.pgpServiceConnection.bindToService();
}
- final PowerManager pm = ContextCompat.getSystemService(this, PowerManager.class);
- this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Conversations:Service");
+ final PowerManager powerManager = getSystemService(PowerManager.class);
+ this.wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Conversations:Service");
toggleForegroundService();
updateUnreadCountBadge();
toggleScreenEventReceiver();
final IntentFilter systemBroadcastFilter = new IntentFilter();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- scheduleNextIdlePing();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- systemBroadcastFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
- }
- systemBroadcastFilter.addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED);
+ scheduleNextIdlePing();
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ systemBroadcastFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
}
+ systemBroadcastFilter.addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED);
ContextCompat.registerReceiver(
this,
this.mInternalEventReceiver,
@@ -1380,23 +1403,21 @@ public class XmppConnectionService extends Service {
ContextCompat.RECEIVER_EXPORTED);
mForceDuringOnCreate.set(false);
toggleForegroundService();
- setupPhoneStateListener();
internalPingExecutor.scheduleAtFixedRate(this::manageAccountConnectionStatesInternal,10,10,TimeUnit.SECONDS);
+ final SharedPreferences sharedPreferences =
+ androidx.preference.PreferenceManager.getDefaultSharedPreferences(this);
+ sharedPreferences.registerOnSharedPreferenceChangeListener(new SharedPreferences.OnSharedPreferenceChangeListener() {
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, @Nullable String key) {
+ Log.d(Config.LOGTAG,"preference '"+key+"' has changed");
+ if (AppSettings.KEEP_FOREGROUND_SERVICE.equals(key)) {
+ toggleForegroundService();
+ }
+ }
+ });
}
- private void setupPhoneStateListener() {
- final TelephonyManager telephonyManager = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
- if (telephonyManager == null || Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
- return;
- }
- telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
- }
-
- public boolean isPhoneInCall() {
- return isPhoneInCall.get();
- }
-
private void checkForDeletedFiles() {
if (destroyed) {
Log.d(Config.LOGTAG, "Do not check for deleted files because service has been destroyed");
@@ -1495,45 +1516,63 @@ public class XmppConnectionService extends Service {
toggleForegroundService(false);
}
- private void toggleForegroundService(boolean force) {
+ private void toggleForegroundService(final boolean force) {
final boolean status;
final OngoingCall ongoing = ongoingCall.get();
- if (force || mForceDuringOnCreate.get() || mForceForegroundService.get() || ongoing != null || (Compatibility.keepForegroundService(this) && hasEnabledAccounts())) {
+ final boolean ongoingVideoTranscoding = mOngoingVideoTranscoding.get();
+ final int id;
+ if (force
+ || mForceDuringOnCreate.get()
+ || ongoingVideoTranscoding
+ || ongoing != null
+ || (Compatibility.keepForegroundService(this) && hasEnabledAccounts())) {
final Notification notification;
- final int id;
if (ongoing != null) {
notification = this.mNotificationService.getOngoingCallNotification(ongoing);
id = NotificationService.ONGOING_CALL_NOTIFICATION_ID;
- startForegroundOrCatch(id, notification);
- mNotificationService.cancel(NotificationService.FOREGROUND_NOTIFICATION_ID);
+ startForegroundOrCatch(id, notification, true);
+ } else if (ongoingVideoTranscoding) {
+ notification = this.mNotificationService.getIndeterminateVideoTranscoding();
+ id = NotificationService.ONGOING_VIDEO_TRANSCODING_NOTIFICATION_ID;
+ startForegroundOrCatch(id, notification, false);
} else {
notification = this.mNotificationService.createForegroundNotification();
id = NotificationService.FOREGROUND_NOTIFICATION_ID;
- startForegroundOrCatch(id, notification);
- }
-
- if (!mForceForegroundService.get()) {
- mNotificationService.notify(id, notification);
+ startForegroundOrCatch(id, notification, false);
}
+ mNotificationService.notify(id, notification);
status = true;
} else {
+ id = 0;
stopForeground(true);
status = false;
}
- if (!mForceForegroundService.get()) {
- mNotificationService.cancel(NotificationService.FOREGROUND_NOTIFICATION_ID);
+
+ for (final int toBeRemoved :
+ Collections2.filter(
+ Arrays.asList(
+ NotificationService.FOREGROUND_NOTIFICATION_ID,
+ NotificationService.ONGOING_CALL_NOTIFICATION_ID,
+ NotificationService.ONGOING_VIDEO_TRANSCODING_NOTIFICATION_ID),
+ i -> i != id)) {
+ mNotificationService.cancel(toBeRemoved);
}
- if (ongoing == null) {
- mNotificationService.cancel(NotificationService.ONGOING_CALL_NOTIFICATION_ID);
- }
- Log.d(Config.LOGTAG, "ForegroundService: " + (status ? "on" : "off"));
+ Log.d(
+ Config.LOGTAG,
+ "ForegroundService: " + (status ? "on" : "off") + ", notification: " + id);
}
- private void startForegroundOrCatch(final int id, final Notification notification) {
+ private void startForegroundOrCatch(
+ final int id, final Notification notification, final boolean requireMicrophone) {
try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
final int foregroundServiceType;
- if (getSystemService(PowerManager.class)
+ if (requireMicrophone
+ && ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
+ == PackageManager.PERMISSION_GRANTED) {
+ foregroundServiceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE;
+ Log.d(Config.LOGTAG, "defaulting to microphone foreground service type");
+ } else if (getSystemService(PowerManager.class)
.isIgnoringBatteryOptimizations(getPackageName())) {
foregroundServiceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED;
} else if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
@@ -1544,7 +1583,7 @@ public class XmppConnectionService extends Service {
foregroundServiceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_CAMERA;
} else {
foregroundServiceType = ServiceInfo.FOREGROUND_SERVICE_TYPE_SPECIAL_USE;
- Log.w(Config.LOGTAG,"falling back to special use foreground service type");
+ Log.w(Config.LOGTAG, "falling back to special use foreground service type");
}
startForeground(id, notification, foregroundServiceType);
} else {
@@ -1556,13 +1595,13 @@ public class XmppConnectionService extends Service {
}
public boolean foregroundNotificationNeedsUpdatingWhenErrorStateChanges() {
- return !mForceForegroundService.get() && ongoingCall.get() == null && Compatibility.keepForegroundService(this) && hasEnabledAccounts();
+ return !mOngoingVideoTranscoding.get() && ongoingCall.get() == null && Compatibility.keepForegroundService(this) && hasEnabledAccounts();
}
@Override
public void onTaskRemoved(final Intent rootIntent) {
super.onTaskRemoved(rootIntent);
- if ((Compatibility.keepForegroundService(this) && hasEnabledAccounts()) || mForceForegroundService.get() || ongoingCall.get() != null) {
+ if ((Compatibility.keepForegroundService(this) && hasEnabledAccounts()) || mOngoingVideoTranscoding.get() || ongoingCall.get() != null) {
Log.d(Config.LOGTAG, "ignoring onTaskRemoved because foreground service is activated");
} else {
this.logoutAndSave(false);
@@ -1592,7 +1631,7 @@ public class XmppConnectionService extends Service {
return;
}
final long triggerAtMillis = SystemClock.elapsedRealtime() + (Config.POST_CONNECTIVITY_CHANGE_PING_INTERVAL * 1000);
- final Intent intent = new Intent(this, EventReceiver.class);
+ final Intent intent = new Intent(this, SystemEventReceiver.class);
intent.setAction(ACTION_POST_CONNECTIVITY_CHANGE);
try {
final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 1, intent, s()
@@ -1614,7 +1653,7 @@ public class XmppConnectionService extends Service {
if (alarmManager == null) {
return;
}
- final Intent intent = new Intent(this, EventReceiver.class);
+ final Intent intent = new Intent(this, SystemEventReceiver.class);
intent.setAction(ACTION_PING);
try {
final PendingIntent pendingIntent =
@@ -1633,7 +1672,7 @@ public class XmppConnectionService extends Service {
if (alarmManager == null) {
return;
}
- final Intent intent = new Intent(this, EventReceiver.class);
+ final Intent intent = new Intent(this, SystemEventReceiver.class);
intent.setAction(ACTION_IDLE_PING);
try {
final PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0, intent, s()
@@ -1939,26 +1978,95 @@ public class XmppConnectionService extends Service {
public void fetchBookmarks2(final Account account) {
final IqPacket retrieve = mIqGenerator.retrieveBookmarks();
- sendIqPacket(account, retrieve, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(final Account account, final IqPacket response) {
- if (response.getType() == IqPacket.TYPE.RESULT) {
- final Element pubsub = response.findChild("pubsub", Namespace.PUBSUB);
- final Map bookmarks = Bookmark.parseFromPubsub(pubsub, account);
- processBookmarksInitial(account, bookmarks, true);
- }
+ sendIqPacket(account, retrieve, (a, response) -> {
+ if (response.getType() == IqPacket.TYPE.RESULT) {
+ final Element pubsub = response.findChild("pubsub", Namespace.PUBSUB);
+ final Map bookmarks = Bookmark.parseFromPubsub(pubsub, a);
+ processBookmarksInitial(a, bookmarks, true);
}
});
}
- public void processBookmarksInitial(Account account, Map bookmarks, final boolean pep) {
- final Set previousBookmarks = account.getBookmarkedJids();
- final boolean synchronizeWithBookmarks = synchronizeWithBookmarks();
- for (Bookmark bookmark : bookmarks.values()) {
- previousBookmarks.remove(bookmark.getJid().asBareJid());
- processModifiedBookmark(bookmark, pep, synchronizeWithBookmarks);
+ private void fetchMessageDisplayedSynchronization(final Account account) {
+ Log.d(Config.LOGTAG, account.getJid() + ": retrieve mds");
+ final var retrieve = mIqGenerator.retrieveMds();
+ sendIqPacket(
+ account,
+ retrieve,
+ (a, response) -> {
+ if (response.getType() != IqPacket.TYPE.RESULT) {
+ return;
+ }
+ final var pubSub = response.findChild("pubsub", Namespace.PUBSUB);
+ final Element items = pubSub == null ? null : pubSub.findChild("items");
+ if (items == null
+ || !Namespace.MDS_DISPLAYED.equals(items.getAttribute("node"))) {
+ return;
+ }
+ for (final Element child : items.getChildren()) {
+ if ("item".equals(child.getName())) {
+ processMdsItem(account, child);
+ }
+ }
+ });
+ }
+
+ public void processMdsItem(final Account account, final Element item) {
+ final Jid jid =
+ item == null ? null : InvalidJid.getNullForInvalid(item.getAttributeAsJid("id"));
+ if (jid == null) {
+ return;
}
- if (pep && synchronizeWithBookmarks) {
+ final Element displayed = item.findChild("displayed", Namespace.MDS_DISPLAYED);
+ final Element stanzaId =
+ displayed == null ? null : displayed.findChild("stanza-id", Namespace.STANZA_IDS);
+ final String id = stanzaId == null ? null : stanzaId.getAttribute("id");
+ final Conversation conversation = find(account, jid);
+ if (id != null && conversation != null) {
+ conversation.setDisplayState(id);
+ markReadUpToStanzaId(conversation, id);
+ }
+ }
+
+ public void markReadUpToStanzaId(final Conversation conversation, final String stanzaId) {
+ final Message message = conversation.findMessageWithServerMsgId(stanzaId);
+ if (message == null) { // do we want to check if isRead?
+ return;
+ }
+ markReadUpTo(conversation, message);
+ }
+
+ public void markReadUpTo(final Conversation conversation, final Message message) {
+ final boolean isDismissNotification = isDismissNotification(message);
+ final var uuid = message.getUuid();
+ Log.d(
+ Config.LOGTAG,
+ conversation.getAccount().getJid().asBareJid()
+ + ": mark "
+ + conversation.getJid().asBareJid()
+ + " as read up to "
+ + uuid);
+ markRead(conversation, uuid, isDismissNotification);
+ }
+
+ private static boolean isDismissNotification(final Message message) {
+ Message next = message.next();
+ while (next != null) {
+ if (message.getStatus() == Message.STATUS_RECEIVED) {
+ return false;
+ }
+ next = next.next();
+ }
+ return true;
+ }
+
+ public void processBookmarksInitial(final Account account, final Map bookmarks, final boolean pep) {
+ final Set previousBookmarks = account.getBookmarkedJids();
+ for (final Bookmark bookmark : bookmarks.values()) {
+ previousBookmarks.remove(bookmark.getJid().asBareJid());
+ processModifiedBookmark(bookmark, pep);
+ }
+ if (pep) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": " + previousBookmarks.size() + " bookmarks have been removed");
for (Jid jid : previousBookmarks) {
processDeletedBookmark(account, jid);
@@ -1975,7 +2083,7 @@ public class XmppConnectionService extends Service {
}
}
- private void processModifiedBookmark(Bookmark bookmark, final boolean pep, final boolean synchronizeWithBookmarks) {
+ private void processModifiedBookmark(final Bookmark bookmark, final boolean pep) {
final Account account = bookmark.getAccount();
Conversation conversation = find(bookmark.getAccount(), bookmark.getJid(), null);
if (conversation != null) {
@@ -1983,7 +2091,7 @@ public class XmppConnectionService extends Service {
return;
}
bookmark.setConversation(conversation);
- if (pep && synchronizeWithBookmarks && !bookmark.autojoin()) {
+ if (pep && !bookmark.autojoin()) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": archiving conference (" + conversation.getJid() + ") after receiving pep");
archiveConversation(conversation, false);
} else {
@@ -1997,15 +2105,14 @@ public class XmppConnectionService extends Service {
}
}
}
- } else if (synchronizeWithBookmarks && bookmark.autojoin()) {
+ } else if (bookmark.autojoin()) {
conversation = findOrCreateConversation(account, bookmark.getFullJid(), null, true, true, false, null);
bookmark.setConversation(conversation);
}
}
- public void processModifiedBookmark(Bookmark bookmark) {
- final boolean synchronizeWithBookmarks = synchronizeWithBookmarks();
- processModifiedBookmark(bookmark, true, synchronizeWithBookmarks);
+ public void processModifiedBookmark(final Bookmark bookmark) {
+ processModifiedBookmark(bookmark, true);
}
public void createBookmark(final Account account, final Bookmark bookmark) {
@@ -2087,7 +2194,7 @@ public class XmppConnectionService extends Service {
}
});
} else {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": error publishing bookmarks (retry=" + retry + ") " + response);
+ Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": error publishing "+node+" (retry=" + retry + ") " + response);
}
});
}
@@ -2667,7 +2774,7 @@ public class XmppConnectionService extends Service {
if (conversation.getNextCounterpart() == null) {
if (conversation.getAccount().getStatus() == Account.State.ONLINE) {
final Bookmark bookmark = conversation.getBookmark();
- if (maySynchronizeWithBookmarks && bookmark != null && synchronizeWithBookmarks()) {
+ if (maySynchronizeWithBookmarks && bookmark != null) {
if (conversation.getMucOptions().getError() == MucOptions.Error.DESTROYED) {
Account account = bookmark.getAccount();
bookmark.setConversation(null);
@@ -2678,8 +2785,8 @@ public class XmppConnectionService extends Service {
}
}
}
- leaveMuc(conversation);
}
+ leaveMuc(conversation);
} else {
if (conversation.getContact().getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
stopPresenceUpdatesTo(conversation.getContact());
@@ -2700,6 +2807,9 @@ public class XmppConnectionService extends Service {
public void createAccount(final Account account) {
account.initAccountServices(this);
databaseBackend.createAccount(account);
+ if (CallIntegration.hasSystemFeature(this)) {
+ CallIntegrationConnectionService.togglePhoneAccountAsync(this, account);
+ }
this.accounts.add(account);
this.reconnectAccountInBackground(account);
updateAccountUi();
@@ -2709,7 +2819,7 @@ public class XmppConnectionService extends Service {
private void syncEnabledAccountSetting() {
final boolean hasEnabledAccounts = hasEnabledAccounts();
- getPreferences().edit().putBoolean(EventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts).apply();
+ getPreferences().edit().putBoolean(SystemEventReceiver.SETTING_ENABLED_ACCOUNTS, hasEnabledAccounts).apply();
toggleSetProfilePictureActivity(hasEnabledAccounts);
}
@@ -2823,6 +2933,9 @@ public class XmppConnectionService extends Service {
toggleForegroundService();
syncEnabledAccountSetting();
mChannelDiscoveryService.cleanCache();
+ if (CallIntegration.hasSystemFeature(this)) {
+ CallIntegrationConnectionService.togglePhoneAccountAsync(this, account);
+ }
return true;
} else {
return false;
@@ -2884,6 +2997,9 @@ public class XmppConnectionService extends Service {
};
mDatabaseWriterExecutor.execute(runnable);
this.accounts.remove(account);
+ if (CallIntegration.hasSystemFeature(this)) {
+ CallIntegrationConnectionService.unregisterPhoneAccount(this, account);
+ }
this.mRosterSyncTaskManager.clear(account);
updateAccountUi();
mNotificationService.updateErrorNotification();
@@ -3420,14 +3536,12 @@ public class XmppConnectionService extends Service {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": fetching members for " + conversation.getName());
}
- public void providePasswordForMuc(Conversation conversation, String password) {
+ public void providePasswordForMuc(final Conversation conversation, final String password) {
if (conversation.getMode() == Conversation.MODE_MULTI) {
conversation.getMucOptions().setPassword(password);
if (conversation.getBookmark() != null) {
final Bookmark bookmark = conversation.getBookmark();
- if (synchronizeWithBookmarks()) {
- bookmark.setAutojoin(true);
- }
+ bookmark.setAutojoin(true);
createBookmark(conversation.getAccount(), bookmark);
}
updateConversation(conversation);
@@ -3526,7 +3640,7 @@ public class XmppConnectionService extends Service {
new Thread(() -> onMediaLoaded.onMediaLoaded(fileBackend.convertToAttachments(databaseBackend.getRelativeFilePaths(account, jid, limit)))).start();
}
- public void persistSelfNick(MucOptions.User self) {
+ public void persistSelfNick(final MucOptions.User self) {
final Conversation conversation = self.getConversation();
final boolean tookProposedNickFromBookmark = conversation.getMucOptions().isTookProposedNickFromBookmark();
Jid full = self.getFullJid();
@@ -3538,11 +3652,10 @@ public class XmppConnectionService extends Service {
final Bookmark bookmark = conversation.getBookmark();
final String bookmarkedNick = bookmark == null ? null : bookmark.getNick();
- if (bookmark != null && (tookProposedNickFromBookmark || TextUtils.isEmpty(bookmarkedNick)) && !full.getResource().equals(bookmarkedNick)) {
+ if (bookmark != null && (tookProposedNickFromBookmark || Strings.isNullOrEmpty(bookmarkedNick)) && !full.getResource().equals(bookmarkedNick)) {
final Account account = conversation.getAccount();
final String defaultNick = MucOptions.defaultNick(account);
- if (TextUtils.isEmpty(bookmarkedNick) && full.getResource().equals(defaultNick)) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": do not overwrite empty bookmark nick with default nick for " + conversation.getJid().asBareJid());
+ if (Strings.isNullOrEmpty(bookmarkedNick) && full.getResource().equals(defaultNick)) {
return;
}
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": persist nick '" + full.getResource() + "' into bookmark for " + conversation.getJid().asBareJid());
@@ -4573,7 +4686,7 @@ public class XmppConnectionService extends Service {
}
public long getAutomaticMessageDeletionDate() {
- final long timeout = getLongPreference(SettingsActivity.AUTOMATIC_MESSAGE_DELETION, R.integer.automatic_message_deletion);
+ final long timeout = getLongPreference(AppSettings.AUTOMATIC_MESSAGE_DELETION, R.integer.automatic_message_deletion);
return timeout == 0 ? timeout : (System.currentTimeMillis() - (timeout * 1000));
}
@@ -4602,20 +4715,16 @@ public class XmppConnectionService extends Service {
return getBooleanPreference("chat_states", R.bool.chat_states);
}
- private boolean synchronizeWithBookmarks() {
- return getBooleanPreference("autojoin", R.bool.autojoin);
- }
-
public boolean useTorToConnect() {
return QuickConversationsService.isConversations() && getBooleanPreference("use_tor", R.bool.use_tor);
}
public boolean showExtendedConnectionOptions() {
- return QuickConversationsService.isConversations() && getBooleanPreference("show_connection_options", R.bool.show_connection_options);
+ return QuickConversationsService.isConversations() && getBooleanPreference(AppSettings.SHOW_CONNECTION_OPTIONS, R.bool.show_connection_options);
}
public boolean broadcastLastActivity() {
- return getBooleanPreference(SettingsActivity.BROADCAST_LAST_ACTIVITY, R.bool.last_activity);
+ return getBooleanPreference(AppSettings.BROADCAST_LAST_ACTIVITY, R.bool.last_activity);
}
public int unreadCount() {
@@ -4629,7 +4738,7 @@ public class XmppConnectionService extends Service {
private List threadSafeList(Set set) {
synchronized (LISTENER_LOCK) {
- return set.size() == 0 ? Collections.emptyList() : new ArrayList<>(set);
+ return set.isEmpty() ? Collections.emptyList() : new ArrayList<>(set);
}
}
@@ -4651,7 +4760,7 @@ public class XmppConnectionService extends Service {
}
}
- public void notifyJingleRtpConnectionUpdate(AppRTCAudioManager.AudioDevice selectedAudioDevice, Set availableAudioDevices) {
+ public void notifyJingleRtpConnectionUpdate(CallIntegration.AudioDevice selectedAudioDevice, Set availableAudioDevices) {
for (OnJingleRtpConnectionUpdate listener : threadSafeList(this.onJingleRtpConnectionUpdate)) {
listener.onAudioDeviceChanged(selectedAudioDevice, availableAudioDevices);
}
@@ -4778,24 +4887,101 @@ public class XmppConnectionService extends Service {
}
}
- public void sendReadMarker(final Conversation conversation, String upToUuid) {
- final boolean isPrivateAndNonAnonymousMuc = conversation.getMode() == Conversation.MODE_MULTI && conversation.isPrivateAndNonAnonymous();
+ public void sendReadMarker(final Conversation conversation, final String upToUuid) {
+ final boolean isPrivateAndNonAnonymousMuc =
+ conversation.getMode() == Conversation.MODE_MULTI
+ && conversation.isPrivateAndNonAnonymous();
final List readMessages = this.markRead(conversation, upToUuid, true);
- if (readMessages.size() > 0) {
- updateConversationUi();
+ if (readMessages.isEmpty()) {
+ return;
}
- final Message markable = Conversation.getLatestMarkableMessage(readMessages, isPrivateAndNonAnonymousMuc);
- if (confirmMessages()
- && markable != null
- && (markable.trusted() || isPrivateAndNonAnonymousMuc)
- && markable.getRemoteMsgId() != null) {
- Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": sending read marker to " + markable.getCounterpart().toString());
- final Account account = conversation.getAccount();
- final MessagePacket packet = mMessageGenerator.confirm(markable);
+ final var account = conversation.getAccount();
+ final var connection = account.getXmppConnection();
+ updateConversationUi();
+ final var last =
+ Iterables.getLast(
+ Collections2.filter(
+ readMessages,
+ m ->
+ !m.isPrivateMessage()
+ && m.getStatus() == Message.STATUS_RECEIVED),
+ null);
+ if (last == null) {
+ return;
+ }
+
+ final boolean sendDisplayedMarker =
+ confirmMessages()
+ && (last.trusted() || isPrivateAndNonAnonymousMuc)
+ && last.getRemoteMsgId() != null
+ && (last.markable || isPrivateAndNonAnonymousMuc);
+ final boolean serverAssist =
+ connection != null && connection.getFeatures().mdsServerAssist();
+
+ final String stanzaId = last.getServerMsgId();
+
+ if (sendDisplayedMarker && serverAssist) {
+ final var mdsDisplayed = mIqGenerator.mdsDisplayed(stanzaId, conversation);
+ final MessagePacket packet = mMessageGenerator.confirm(last);
+ packet.addChild(mdsDisplayed);
+ if (!last.isPrivateMessage()) {
+ packet.setTo(packet.getTo().asBareJid());
+ }
+ Log.d(Config.LOGTAG,account.getJid().asBareJid()+": server assisted "+packet);
this.sendMessagePacket(account, packet);
+ } else {
+ publishMds(last);
+ // read markers will be sent after MDS to flush the CSI stanza queue
+ if (sendDisplayedMarker) {
+ Log.d(
+ Config.LOGTAG,
+ conversation.getAccount().getJid().asBareJid()
+ + ": sending displayed marker to "
+ + last.getCounterpart().toString());
+ final MessagePacket packet = mMessageGenerator.confirm(last);
+ this.sendMessagePacket(account, packet);
+ }
}
}
+ private void publishMds(@Nullable final Message message) {
+ final String stanzaId = message == null ? null : message.getServerMsgId();
+ if (Strings.isNullOrEmpty(stanzaId)) {
+ return;
+ }
+ final Conversation conversation;
+ final var conversational = message.getConversation();
+ if (conversational instanceof Conversation c) {
+ conversation = c;
+ } else {
+ return;
+ }
+ final var account = conversation.getAccount();
+ final var connection = account.getXmppConnection();
+ if (connection == null || !connection.getFeatures().mds()) {
+ return;
+ }
+ final Jid itemId;
+ if (message.isPrivateMessage()) {
+ itemId = message.getCounterpart();
+ } else {
+ itemId = conversation.getJid().asBareJid();
+ }
+ Log.d(Config.LOGTAG,"publishing mds for "+itemId+"/"+stanzaId);
+ publishMds(account, itemId, stanzaId, conversation);
+ }
+
+ private void publishMds(
+ final Account account, final Jid itemId, final String stanzaId, final Conversation conversation) {
+ final var item = mIqGenerator.mdsDisplayed(stanzaId, conversation);
+ pushNodeAndEnforcePublishOptions(
+ account,
+ Namespace.MDS_DISPLAYED,
+ item,
+ itemId.toEscapedString(),
+ PublishOptions.persistentWhitelistAccessMaxItems());
+ }
+
public MemorizingTrustManager getMemorizingTrustManager() {
return this.mMemorizingTrustManager;
}
@@ -4804,15 +4990,15 @@ public class XmppConnectionService extends Service {
this.mMemorizingTrustManager = trustManager;
}
- public void updateMemorizingTrustmanager() {
- final MemorizingTrustManager tm;
- final boolean dontTrustSystemCAs = getBooleanPreference("dont_trust_system_cas", R.bool.dont_trust_system_cas);
- if (dontTrustSystemCAs) {
- tm = new MemorizingTrustManager(getApplicationContext(), null);
+ public void updateMemorizingTrustManager() {
+ final MemorizingTrustManager trustManager;
+ final var appSettings = new AppSettings(this);
+ if (appSettings.isTrustSystemCAStore()) {
+ trustManager = new MemorizingTrustManager(getApplicationContext());
} else {
- tm = new MemorizingTrustManager(getApplicationContext());
+ trustManager = new MemorizingTrustManager(getApplicationContext(), null);
}
- setMemorizingTrustManager(tm);
+ setMemorizingTrustManager(trustManager);
}
public LruCache getBitmapCache() {
@@ -4839,9 +5025,6 @@ public class XmppConnectionService extends Service {
if (Config.QUICKSY_DOMAIN != null) {
hosts.remove(Config.QUICKSY_DOMAIN.toEscapedString()); //we only want to show this when we type a e164 number
}
- if (Config.DOMAIN_LOCK != null) {
- hosts.add(Config.DOMAIN_LOCK);
- }
if (Config.MAGIC_CREATE_DOMAIN != null) {
hosts.add(Config.MAGIC_CREATE_DOMAIN);
}
@@ -5266,7 +5449,7 @@ public class XmppConnectionService extends Service {
return templates;
}
- public void saveConversationAsBookmark(Conversation conversation, String name) {
+ public void saveConversationAsBookmark(final Conversation conversation, final String name) {
final Account account = conversation.getAccount();
final Bookmark bookmark = new Bookmark(account, conversation.getJid().asBareJid());
final String nick = conversation.getJid().getResource();
@@ -5276,7 +5459,7 @@ public class XmppConnectionService extends Service {
if (!TextUtils.isEmpty(name)) {
bookmark.setBookmarkName(name);
}
- bookmark.setAutojoin(getPreferences().getBoolean("autojoin", getResources().getBoolean(R.bool.autojoin)));
+ bookmark.setAutojoin(true);
createBookmark(account, bookmark);
bookmark.setConversation(conversation);
}
@@ -5324,7 +5507,7 @@ public class XmppConnectionService extends Service {
}
public boolean blindTrustBeforeVerification() {
- return getBooleanPreference(SettingsActivity.BLIND_TRUST_BEFORE_VERIFICATION, R.bool.btbv);
+ return getBooleanPreference(AppSettings.BLIND_TRUST_BEFORE_VERIFICATION, R.bool.btbv);
}
public ShortcutService getShortcutService() {
@@ -5391,7 +5574,7 @@ public class XmppConnectionService extends Service {
public interface OnJingleRtpConnectionUpdate {
void onJingleRtpConnectionUpdate(final Account account, final Jid with, final String sessionId, final RtpEndUserState state);
- void onAudioDeviceChanged(AppRTCAudioManager.AudioDevice selectedAudioDevice, Set availableAudioDevices);
+ void onAudioDeviceChanged(CallIntegration.AudioDevice selectedAudioDevice, Set availableAudioDevices);
}
public interface OnAccountUpdate {
diff --git a/src/main/java/eu/siacs/conversations/ui/AboutPreference.java b/src/main/java/eu/siacs/conversations/ui/AboutPreference.java
index f2cd1e348..6ea969693 100644
--- a/src/main/java/eu/siacs/conversations/ui/AboutPreference.java
+++ b/src/main/java/eu/siacs/conversations/ui/AboutPreference.java
@@ -2,26 +2,28 @@ package eu.siacs.conversations.ui;
import android.content.Context;
import android.content.Intent;
+import android.os.Build;
import android.preference.Preference;
import android.util.AttributeSet;
+import com.google.common.base.Strings;
+
import eu.siacs.conversations.BuildConfig;
import eu.siacs.conversations.R;
-import eu.siacs.conversations.utils.PhoneHelper;
public class AboutPreference extends Preference {
- public AboutPreference(final Context context, final AttributeSet attrs, final int defStyle) {
- super(context, attrs, defStyle);
+ public AboutPreference(final Context context, final AttributeSet attrs, final int defStyle) {
+ super(context, attrs, defStyle);
setSummaryAndTitle(context);
- }
+ }
- public AboutPreference(final Context context, final AttributeSet attrs) {
- super(context, attrs);
- setSummaryAndTitle(context);
- }
+ public AboutPreference(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ setSummaryAndTitle(context);
+ }
- private void setSummaryAndTitle(final Context context) {
- setSummary(String.format("%s %s", BuildConfig.APP_NAME, BuildConfig.VERSION_NAME));
+ private void setSummaryAndTitle(final Context context) {
+ setSummary(String.format("%s %s %s (%s)", BuildConfig.APP_NAME, BuildConfig.VERSION_NAME, im.conversations.webrtc.BuildConfig.WEBRTC_VERSION, Strings.nullToEmpty(Build.DEVICE)));
setTitle(context.getString(R.string.title_activity_about_x, BuildConfig.APP_NAME));
}
@@ -32,4 +34,3 @@ public class AboutPreference extends Preference {
getContext().startActivity(intent);
}
}
-
diff --git a/src/main/java/eu/siacs/conversations/ui/AbstractSearchableListItemActivity.java b/src/main/java/eu/siacs/conversations/ui/AbstractSearchableListItemActivity.java
index 21cf7d80f..0479598f8 100644
--- a/src/main/java/eu/siacs/conversations/ui/AbstractSearchableListItemActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/AbstractSearchableListItemActivity.java
@@ -14,6 +14,7 @@ import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
+import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil;
import java.util.ArrayList;
@@ -35,7 +36,7 @@ public abstract class AbstractSearchableListItemActivity extends XmppActivity im
private final MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() {
@Override
- public boolean onMenuItemActionExpand(final MenuItem item) {
+ public boolean onMenuItemActionExpand(@NonNull final MenuItem item) {
mSearchEditText.post(() -> {
mSearchEditText.requestFocus();
final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
@@ -46,7 +47,7 @@ public abstract class AbstractSearchableListItemActivity extends XmppActivity im
}
@Override
- public boolean onMenuItemActionCollapse(final MenuItem item) {
+ public boolean onMenuItemActionCollapse(@NonNull final MenuItem item) {
final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mSearchEditText.getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY);
mSearchEditText.setText("");
@@ -93,6 +94,7 @@ public abstract class AbstractSearchableListItemActivity extends XmppActivity im
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.binding = DataBindingUtil.setContentView(this,R.layout.activity_choose_contact);
+ Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
setSupportActionBar(binding.toolbar);
configureActionBar(getSupportActionBar());
this.binding.chooseContactList.setFastScrollEnabled(true);
@@ -125,7 +127,7 @@ public abstract class AbstractSearchableListItemActivity extends XmppActivity im
protected abstract void filterContacts(final String needle);
@Override
- void onBackendConnected() {
+ protected void onBackendConnected() {
filterContacts();
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ActionBarActivity.java b/src/main/java/eu/siacs/conversations/ui/ActionBarActivity.java
index 8564bcfcb..42c5981c2 100644
--- a/src/main/java/eu/siacs/conversations/ui/ActionBarActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ActionBarActivity.java
@@ -1,14 +1,10 @@
package eu.siacs.conversations.ui;
import android.view.MenuItem;
-import android.view.View;
import androidx.appcompat.app.ActionBar;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.appcompat.widget.Toolbar;
-
-public abstract class ActionBarActivity extends AppCompatActivity {
+public abstract class ActionBarActivity extends BaseActivity {
public static void configureActionBar(ActionBar actionBar) {
configureActionBar(actionBar, true);
}
@@ -20,17 +16,11 @@ public abstract class ActionBarActivity extends AppCompatActivity {
}
}
- public void setSupportActionBar(View toolbar) {
- super.setSupportActionBar((Toolbar) toolbar);
- }
-
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case android.R.id.home:
- finish();
- break;
+ if (item.getItemId() == android.R.id.home) {
+ finish();
}
return super.onOptionsItemSelected(item);
}
-}
\ No newline at end of file
+}
diff --git a/src/main/java/eu/siacs/conversations/ui/Activities.java b/src/main/java/eu/siacs/conversations/ui/Activities.java
new file mode 100644
index 000000000..d95d6b4ac
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/ui/Activities.java
@@ -0,0 +1,52 @@
+package eu.siacs.conversations.ui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.view.View;
+import com.google.android.material.elevation.SurfaceColors;
+
+public final class Activities {
+
+ private Activities() {}
+
+ public static void setStatusAndNavigationBarColors(final Activity activity, final View view) {
+ setStatusAndNavigationBarColors(activity, view, false);
+ }
+
+ public static void setStatusAndNavigationBarColors(
+ final Activity activity, final View view, final boolean raisedStatusBar) {
+ final var isLightMode = isLightMode(activity);
+ final var window = activity.getWindow();
+ final var flags = view.getSystemUiVisibility();
+ // an elevation of 4 matches the MaterialToolbar elevation
+ if (raisedStatusBar) {
+ window.setStatusBarColor(SurfaceColors.SURFACE_5.getColor(activity));
+ } else {
+ window.setStatusBarColor(SurfaceColors.SURFACE_0.getColor(activity));
+ }
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ window.setNavigationBarColor(SurfaceColors.SURFACE_1.getColor(activity));
+ if (isLightMode) {
+ view.setSystemUiVisibility(
+ flags
+ | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
+ | View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR);
+ }
+ } else if (isLightMode) {
+ view.setSystemUiVisibility(flags | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
+ }
+ }
+
+ private static boolean isLightMode(final Context context) {
+ final int nightModeFlags =
+ context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
+ return nightModeFlags != Configuration.UI_MODE_NIGHT_YES;
+ }
+
+ public static boolean isNightMode(final Context context) {
+ return (context.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
+ == Configuration.UI_MODE_NIGHT_YES;
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/ui/BaseActivity.java b/src/main/java/eu/siacs/conversations/ui/BaseActivity.java
new file mode 100644
index 000000000..cea58c15e
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/ui/BaseActivity.java
@@ -0,0 +1,53 @@
+package eu.siacs.conversations.ui;
+
+import android.util.Log;
+
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.app.AppCompatDelegate;
+
+import eu.siacs.conversations.Conversations;
+import eu.siacs.conversations.ui.util.SettingsUtils;
+
+public abstract class BaseActivity extends AppCompatActivity {
+ private Boolean isDynamicColors;
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ final int desiredNightMode = Conversations.getDesiredNightMode(this);
+ if (setDesiredNightMode(desiredNightMode)) {
+ return;
+ }
+ final boolean isDynamicColors = Conversations.isDynamicColorsDesired(this);
+ setDynamicColors(isDynamicColors);
+ }
+
+ @Override
+ protected void onResume(){
+ super.onResume();
+ SettingsUtils.applyScreenshotSetting(this);
+ }
+
+ public void setDynamicColors(final boolean isDynamicColors) {
+ if (this.isDynamicColors == null) {
+ this.isDynamicColors = isDynamicColors;
+ } else {
+ if (this.isDynamicColors != isDynamicColors) {
+ Log.i(
+ "Recreating {} because dynamic color setting has changed",
+ getClass().getSimpleName());
+ recreate();
+ }
+ }
+ }
+
+ public boolean setDesiredNightMode(final int desiredNightMode) {
+ if (desiredNightMode == AppCompatDelegate.getDefaultNightMode()) {
+ return false;
+ }
+ AppCompatDelegate.setDefaultNightMode(desiredNightMode);
+ Log.i("Recreating {} because desired night mode has changed", getClass().getSimpleName());
+ recreate();
+ return true;
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java b/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java
index 986aeb563..04678c3c7 100644
--- a/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java
+++ b/src/main/java/eu/siacs/conversations/ui/BlockContactDialog.java
@@ -7,6 +7,8 @@ import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.databinding.DataBindingUtil;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.DialogBlockContactBinding;
import eu.siacs.conversations.entities.Blockable;
@@ -19,7 +21,7 @@ public final class BlockContactDialog {
show(xmppActivity, blockable, null);
}
public static void show(final XmppActivity xmppActivity, final Blockable blockable, final String serverMsgId) {
- final AlertDialog.Builder builder = new AlertDialog.Builder(xmppActivity);
+ final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(xmppActivity);
final boolean isBlocked = blockable.isBlocked();
builder.setNegativeButton(R.string.cancel, null);
DialogBlockContactBinding binding = DataBindingUtil.inflate(xmppActivity.getLayoutInflater(), R.layout.dialog_block_contact, null, false);
@@ -70,7 +72,7 @@ public final class BlockContactDialog {
} else {
boolean toastShown = false;
if (xmppActivity.xmppConnectionService.sendBlockRequest(blockable, binding.reportSpam.isChecked(), serverMsgId)) {
- Toast.makeText(xmppActivity, R.string.corresponding_conversations_closed, Toast.LENGTH_SHORT).show();
+ Toast.makeText(xmppActivity, R.string.corresponding_chats_closed, Toast.LENGTH_SHORT).show();
toastShown = true;
}
if (xmppActivity instanceof ContactDetailsActivity) {
diff --git a/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java b/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java
index 21a90ffd8..20eac8bc3 100644
--- a/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/BlocklistActivity.java
@@ -90,7 +90,7 @@ public class BlocklistActivity extends AbstractSearchableListItemActivity implem
dialog.setOnEnterJidDialogPositiveListener((accountJid, contactJid, x, y) -> {
Blockable blockable = new RawBlockable(account, contactJid);
if (xmppConnectionService.sendBlockRequest(blockable, false, null)) {
- Toast.makeText(BlocklistActivity.this, R.string.corresponding_conversations_closed, Toast.LENGTH_SHORT).show();
+ Toast.makeText(BlocklistActivity.this, R.string.corresponding_chats_closed, Toast.LENGTH_SHORT).show();
}
return true;
});
diff --git a/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java b/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java
index c2a334821..fba273b0a 100644
--- a/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ChangePasswordActivity.java
@@ -3,86 +3,84 @@ package eu.siacs.conversations.ui;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
-import android.widget.Button;
-import android.widget.EditText;
import android.widget.Toast;
+import androidx.databinding.DataBindingUtil;
+
import com.google.android.material.textfield.TextInputLayout;
import eu.siacs.conversations.R;
+import eu.siacs.conversations.databinding.ActivityChangePasswordBinding;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.ui.widget.DisabledActionModeCallback;
public class ChangePasswordActivity extends XmppActivity implements XmppConnectionService.OnAccountPasswordChanged {
- private Button mChangePasswordButton;
+ private ActivityChangePasswordBinding binding;
+
private final View.OnClickListener mOnChangePasswordButtonClicked = new View.OnClickListener() {
@Override
- public void onClick(View view) {
- if (mAccount != null) {
- final String currentPassword = mCurrentPassword.getText().toString();
- final String newPassword = mNewPassword.getText().toString();
- if (!mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) && !currentPassword.equals(mAccount.getPassword())) {
- mCurrentPassword.requestFocus();
- mCurrentPasswordLayout.setError(getString(R.string.account_status_unauthorized));
- removeErrorsOnAllBut(mCurrentPasswordLayout);
- } else if (newPassword.trim().isEmpty()) {
- mNewPassword.requestFocus();
- mNewPasswordLayout.setError(getString(R.string.password_should_not_be_empty));
- removeErrorsOnAllBut(mNewPasswordLayout);
- } else {
- mCurrentPasswordLayout.setError(null);
- mNewPasswordLayout.setError(null);
- xmppConnectionService.updateAccountPasswordOnServer(mAccount, newPassword, ChangePasswordActivity.this);
- mChangePasswordButton.setEnabled(false);
- mChangePasswordButton.setText(R.string.updating);
- }
+ public void onClick(final View view) {
+ final var account = mAccount;
+ if (account == null) {
+ return;
}
+ final String currentPassword = binding.currentPassword.getText().toString();
+ final String newPassword = binding.newPassword.getText().toString();
+ if (!account.isOptionSet(Account.OPTION_MAGIC_CREATE) && !currentPassword.equals(account.getPassword())) {
+ binding.currentPassword.requestFocus();
+ binding.currentPasswordLayout.setError(getString(R.string.account_status_unauthorized));
+ removeErrorsOnAllBut(binding.currentPasswordLayout);
+ } else if (newPassword.trim().isEmpty()) {
+ binding.newPassword.requestFocus();
+ binding.newPasswordLayout.setError(getString(R.string.password_should_not_be_empty));
+ removeErrorsOnAllBut(binding.newPasswordLayout);
+ } else {
+ binding.currentPasswordLayout.setError(null);
+ binding.newPasswordLayout.setError(null);
+ xmppConnectionService.updateAccountPasswordOnServer(account, newPassword, ChangePasswordActivity.this);
+ binding.changePasswordButton.setEnabled(false);
+ binding.changePasswordButton.setText(R.string.updating);
+ }
}
};
- private EditText mCurrentPassword;
- private EditText mNewPassword;
- private TextInputLayout mNewPasswordLayout;
- private TextInputLayout mCurrentPasswordLayout;
+
+
+
private Account mAccount;
@Override
- void onBackendConnected() {
+ protected void onBackendConnected() {
this.mAccount = extractAccount(getIntent());
if (this.mAccount != null && this.mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) {
- this.mCurrentPasswordLayout.setVisibility(View.GONE);
+ this.binding.currentPasswordLayout.setVisibility(View.GONE);
} else {
- this.mCurrentPassword.setVisibility(View.VISIBLE);
+ this.binding.currentPasswordLayout.setVisibility(View.VISIBLE);
}
}
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_change_password);
- setSupportActionBar(findViewById(R.id.toolbar));
+ this.binding = DataBindingUtil.setContentView(this, R.layout.activity_change_password);
+ Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
+ setSupportActionBar(binding.toolbar);
configureActionBar(getSupportActionBar());
- Button mCancelButton = findViewById(R.id.left_button);
- mCancelButton.setOnClickListener(view -> finish());
- this.mChangePasswordButton = findViewById(R.id.right_button);
- this.mChangePasswordButton.setOnClickListener(this.mOnChangePasswordButtonClicked);
- this.mCurrentPassword = findViewById(R.id.current_password);
- this.mCurrentPassword.setCustomSelectionActionModeCallback(new DisabledActionModeCallback());
- this.mNewPassword = findViewById(R.id.new_password);
- this.mNewPassword.setCustomSelectionActionModeCallback(new DisabledActionModeCallback());
- this.mCurrentPasswordLayout = findViewById(R.id.current_password_layout);
- this.mNewPasswordLayout = findViewById(R.id.new_password_layout);
+ binding.cancelButton.setOnClickListener(view -> finish());
+ binding.changePasswordButton.setOnClickListener(this.mOnChangePasswordButtonClicked);
+ binding.currentPassword.setCustomSelectionActionModeCallback(new DisabledActionModeCallback());
+ binding.newPassword.setCustomSelectionActionModeCallback(new DisabledActionModeCallback());
}
@Override
- protected void onStart() {
+ public void onStart() {
super.onStart();
Intent intent = getIntent();
String password = intent != null ? intent.getStringExtra("password") : null;
if (password != null) {
- this.mNewPassword.getEditableText().clear();
- this.mNewPassword.getEditableText().append(password);
+ binding.newPassword.getEditableText().clear();
+ binding.newPassword.getEditableText().append(password);
}
}
@@ -97,21 +95,21 @@ public class ChangePasswordActivity extends XmppActivity implements XmppConnecti
@Override
public void onPasswordChangeFailed() {
runOnUiThread(() -> {
- mNewPasswordLayout.setError(getString(R.string.could_not_change_password));
- mChangePasswordButton.setEnabled(true);
- mChangePasswordButton.setText(R.string.change_password);
+ binding.newPasswordLayout.setError(getString(R.string.could_not_change_password));
+ binding.changePasswordButton.setEnabled(true);
+ binding.changePasswordButton.setText(R.string.change_password);
});
}
private void removeErrorsOnAllBut(TextInputLayout exception) {
- if (this.mCurrentPasswordLayout != exception) {
- this.mCurrentPasswordLayout.setErrorEnabled(false);
- this.mCurrentPasswordLayout.setError(null);
+ if (this.binding.currentPasswordLayout != exception) {
+ this.binding.currentPasswordLayout.setErrorEnabled(false);
+ this.binding.currentPasswordLayout.setError(null);
}
- if (this.mNewPasswordLayout != exception) {
- this.mNewPasswordLayout.setErrorEnabled(false);
- this.mNewPasswordLayout.setError(null);
+ if (this.binding.newPasswordLayout != exception) {
+ this.binding.newPasswordLayout.setErrorEnabled(false);
+ this.binding.newPasswordLayout.setError(null);
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java b/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java
index 4568a5abe..f114a83db 100644
--- a/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java
@@ -18,6 +18,8 @@ import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
+import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
import androidx.databinding.DataBindingUtil;
import com.google.common.base.Strings;
@@ -62,7 +64,7 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
}
@Override
- void onBackendConnected() {
+ protected void onBackendConnected() {
if (optedIn || method == ChannelDiscoveryService.Method.LOCAL_SERVER) {
final String query;
if (mMenuSearchView != null && mMenuSearchView.isActionViewExpanded()) {
@@ -130,7 +132,7 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
}
@Override
- public boolean onMenuItemActionExpand(MenuItem item) {
+ public boolean onMenuItemActionExpand(@NonNull MenuItem item) {
mSearchEditText.post(() -> {
mSearchEditText.requestFocus();
final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
@@ -140,7 +142,7 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
}
@Override
- public boolean onMenuItemActionCollapse(MenuItem item) {
+ public boolean onMenuItemActionCollapse(@NonNull MenuItem item) {
final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mSearchEditText.getWindowToken(), InputMethodManager.HIDE_IMPLICIT_ONLY);
mSearchEditText.setText("");
@@ -189,7 +191,7 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
}
@Override
- public void onSaveInstanceState(Bundle savedInstanceState) {
+ public void onSaveInstanceState(@NonNull Bundle savedInstanceState) {
if (mMenuSearchView != null && mMenuSearchView.isActionViewExpanded()) {
savedInstanceState.putString("search", mSearchEditText != null ? mSearchEditText.getText().toString() : null);
}
@@ -219,8 +221,8 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
runOnUiThread(() -> {
adapter.submitList(results);
binding.progressBar.setVisibility(View.GONE);
- if (results.size() == 0) {
- binding.list.setBackground(StyledAttributes.getDrawable(this, R.attr.activity_primary_background_no_results));
+ if (results.isEmpty()) {
+ binding.list.setBackground(ContextCompat.getDrawable(this,R.drawable.background_no_results));
} else {
binding.list.setBackgroundColor(StyledAttributes.getColor(this, R.attr.color_background_primary));
}
@@ -233,7 +235,7 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
final List accounts = AccountUtils.getEnabledAccounts(xmppConnectionService);
if (accounts.size() == 1) {
joinChannelSearchResult(accounts.get(0), result);
- } else if (accounts.size() == 0) {
+ } else if (accounts.isEmpty()) {
Toast.makeText(this, R.string.please_enable_an_account, Toast.LENGTH_LONG).show();
} else {
final AtomicReference account = new AtomicReference<>(accounts.get(0));
@@ -248,40 +250,44 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O
}
@Override
- public boolean onContextItemSelected(MenuItem item) {
+ public boolean onContextItemSelected(@NonNull MenuItem item) {
final Room room = adapter.getCurrent();
- if (room != null) {
- switch (item.getItemId()) {
- case R.id.share_with:
- StartConversationActivity.shareAsChannel(this, room.address);
- return true;
- case R.id.open_join_dialog:
- final Intent intent = new Intent(this, StartConversationActivity.class);
- intent.setAction(Intent.ACTION_VIEW);
- intent.putExtra("force_dialog", true);
- intent.setData(Uri.parse(String.format("xmpp:%s?join", room.address)));
- startActivity(intent);
- return true;
- }
+ if (room == null) {
+ return false;
+ }
+ final int itemId = item.getItemId();
+ if (itemId == R.id.share_with) {
+ StartConversationActivity.shareAsChannel(this, room.address);
+ return true;
+ } else if (itemId == R.id.open_join_dialog) {
+ final Intent intent = new Intent(this, StartConversationActivity.class);
+ intent.setAction(Intent.ACTION_VIEW);
+ intent.putExtra("force_dialog", true);
+ intent.setData(Uri.parse(String.format("xmpp:%s?join", room.address)));
+ startActivity(intent);
+ return true;
+ } else {
+ return false;
}
- return false;
}
- public void joinChannelSearchResult(String selectedAccount, Room result) {
- final Jid jid = Config.DOMAIN_LOCK == null ? Jid.ofEscaped(selectedAccount) : Jid.ofLocalAndDomainEscaped(selectedAccount, Config.DOMAIN_LOCK);
- final boolean syncAutoJoin = getBooleanPreference("autojoin", R.bool.autojoin);
+ public void joinChannelSearchResult(final String selectedAccount, final Room result) {
+ final Jid jid = Jid.ofEscaped(selectedAccount);
final Account account = xmppConnectionService.findAccountByJid(jid);
- final Conversation conversation = xmppConnectionService.findOrCreateConversation(account, result.getRoom(), null, true, true, true, null);
- Bookmark bookmark = conversation.getBookmark();
- if (bookmark != null) {
- if (!bookmark.autojoin() && syncAutoJoin) {
- bookmark.setAutojoin(true);
- xmppConnectionService.createBookmark(account, bookmark);
- }
- } else {
- bookmark = new Bookmark(account, conversation.getJid().asBareJid());
- bookmark.setAutojoin(syncAutoJoin);
+ final Conversation conversation =
+ xmppConnectionService.findOrCreateConversation(
+ account, result.getRoom(), null, true, true, true, null);
+
+ final var existingBookmark = conversation.getBookmark();
+ if (existingBookmark == null) {
+ final var bookmark = new Bookmark(account, conversation.getJid().asBareJid());
+ bookmark.setAutojoin(true);
xmppConnectionService.createBookmark(account, bookmark);
+ } else {
+ if (!existingBookmark.autojoin()) {
+ existingBookmark.setAutojoin(true);
+ xmppConnectionService.createBookmark(account, existingBookmark);
+ }
}
switchToConversation(conversation);
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ChooseAccountForProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/ChooseAccountForProfilePictureActivity.java
index af1fb7656..71662589c 100644
--- a/src/main/java/eu/siacs/conversations/ui/ChooseAccountForProfilePictureActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ChooseAccountForProfilePictureActivity.java
@@ -3,20 +3,21 @@ package eu.siacs.conversations.ui;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
-import android.widget.ListView;
import android.widget.Toast;
+import androidx.databinding.DataBindingUtil;
+
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.databinding.ActivityManageAccountsBinding;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.ui.adapter.AccountAdapter;
+
import java.util.ArrayList;
import java.util.List;
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.ui.adapter.AccountAdapter;
-
public class ChooseAccountForProfilePictureActivity extends XmppActivity {
protected final List accountList = new ArrayList<>();
- protected ListView accountListView;
protected AccountAdapter mAccountAdapter;
@Override
@@ -28,29 +29,25 @@ public class ChooseAccountForProfilePictureActivity extends XmppActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_manage_accounts);
- setSupportActionBar(findViewById(R.id.toolbar));
+ final ActivityManageAccountsBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_manage_accounts);
+ Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
+ setSupportActionBar(binding.toolbar);
configureActionBar(getSupportActionBar(), false);
- accountListView = findViewById(R.id.account_list);
this.mAccountAdapter = new AccountAdapter(this, accountList, false);
- accountListView.setAdapter(this.mAccountAdapter);
- accountListView.setOnItemClickListener((arg0, view, position, arg3) -> {
+ binding.accountList.setAdapter(this.mAccountAdapter);
+ binding.accountList.setOnItemClickListener((arg0, view, position, arg3) -> {
final Account account = accountList.get(position);
goToProfilePictureActivity(account);
});
}
@Override
- protected void onStart() {
+ public void onStart() {
super.onStart();
- final int theme = findTheme();
- if (this.mTheme != theme) {
- recreate();
- }
}
@Override
- void onBackendConnected() {
+ protected void onBackendConnected() {
loadEnabledAccounts();
if (accountList.size() == 1) {
goToProfilePictureActivity(accountList.get(0));
diff --git a/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java b/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java
index d6c80b61b..41237f392 100644
--- a/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ChooseContactActivity.java
@@ -9,6 +9,7 @@ import android.view.ActionMode;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
+import android.view.SoundEffectConstants;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.AbsListView.MultiChoiceModeListener;
@@ -51,7 +52,7 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im
public static final String EXTRA_SHOW_ENTER_JID = "extra_show_enter_jid";
public static final String EXTRA_CONVERSATION = "extra_conversation";
private static final String EXTRA_FILTERED_CONTACTS = "extra_filtered_contacts";
- private final List mActivatedAccounts = new ArrayList<>();
+ private final ArrayList mActivatedAccounts = new ArrayList<>();
private final Set selected = new HashSet<>();
private Set filterContacts;
@@ -130,7 +131,7 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im
if (this.showEnterJid) {
this.binding.fab.show();
} else {
- binding.fab.setImageResource(R.drawable.ic_forward_white_24dp);
+ binding.fab.setImageResource(R.drawable.ic_navigate_next_24dp);
}
final SharedPreferences preferences = getPreferences();
@@ -148,7 +149,7 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im
}
private void onFabClicked(View v) {
- if (selected.size() == 0) {
+ if (selected.isEmpty()) {
showEnterJidDialog(null);
} else {
submitSelection();
@@ -163,7 +164,8 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im
@Override
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
mode.setTitle(getTitleFromIntent());
- binding.fab.setImageResource(R.drawable.ic_forward_white_24dp);
+ binding.chooseContactList.setFastScrollEnabled(false);
+ binding.fab.setImageResource(R.drawable.ic_navigate_next_24dp);
binding.fab.show();
final View view = getSearchEditText();
final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
@@ -175,12 +177,13 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im
@Override
public void onDestroyActionMode(ActionMode mode) {
- this.binding.fab.setImageResource(R.drawable.ic_person_add_white_24dp);
+ this.binding.fab.setImageResource(R.drawable.ic_person_add_24dp);
if (this.showEnterJid) {
this.binding.fab.show();
} else {
this.binding.fab.hide();
}
+ binding.chooseContactList.setFastScrollEnabled(true);
selected.clear();
}
@@ -208,8 +211,9 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im
@Override
public void onItemCheckedStateChanged(ActionMode mode, int position, long id, boolean checked) {
if (selected.size() != 0) {
- getListView().playSoundEffect(0);
+ getListView().playSoundEffect(SoundEffectConstants.CLICK);
}
+ getListItemAdapter().notifyDataSetChanged();
Contact item = (Contact) getListItems().get(position);
if (checked) {
selected.add(item.getJid().toString());
@@ -378,16 +382,12 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im
}
@Override
- void onBackendConnected() {
+ protected void onBackendConnected() {
filterContacts();
this.mActivatedAccounts.clear();
- for (Account account : xmppConnectionService.getAccounts()) {
+ for (final Account account : xmppConnectionService.getAccounts()) {
if (account.isEnabled()) {
- if (Config.DOMAIN_LOCK != null) {
- this.mActivatedAccounts.add(account.getJid().getEscapedLocal());
- } else {
- this.mActivatedAccounts.add(account.getJid().asBareJid().toEscapedString());
- }
+ this.mActivatedAccounts.add(account.getJid().asBareJid().toEscapedString());
}
}
ActivityResult activityResult = this.postponedActivityResult.pop();
diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
index 89c5f9a52..dd80bf8bd 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
@@ -244,7 +244,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
}
@Override
- protected void onStart() {
+ public void onStart() {
super.onStart();
final int theme = findTheme();
if (this.mTheme != theme) {
@@ -271,9 +271,6 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
case R.id.action_save_as_bookmark:
saveAsBookmark();
break;
- case R.id.action_delete_bookmark:
- deleteBookmark();
- break;
case R.id.action_destroy_room:
destroyRoom();
break;
@@ -308,7 +305,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
final MucOptions mucOptions = mConversation.getMucOptions();
this.binding.mucEditor.setVisibility(View.VISIBLE);
this.binding.mucDisplay.setVisibility(View.GONE);
- this.binding.editMucNameButton.setImageResource(getThemeResource(R.attr.icon_cancel, R.drawable.ic_cancel_black_24dp));
+ this.binding.editMucNameButton.setImageResource(R.drawable.ic_cancel_24dp);
final String name = mucOptions.getName();
this.binding.mucEditTitle.setText("");
final boolean owner = mucOptions.getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER);
@@ -382,7 +379,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
private void hideEditor() {
this.binding.mucEditor.setVisibility(View.GONE);
this.binding.mucDisplay.setVisibility(View.VISIBLE);
- this.binding.editMucNameButton.setImageResource(getThemeResource(R.attr.icon_edit_body, R.drawable.ic_edit_black_24dp));
+ this.binding.editMucNameButton.setImageResource(R.drawable.ic_edit_24dp);
}
private void onMucInfoUpdated(String subject, String name) {
@@ -413,28 +410,21 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
}
@Override
- public boolean onPrepareOptionsMenu(Menu menu) {
- MenuItem menuItemSaveBookmark = menu.findItem(R.id.action_save_as_bookmark);
- MenuItem menuItemDeleteBookmark = menu.findItem(R.id.action_delete_bookmark);
- MenuItem menuItemAdvancedMode = menu.findItem(R.id.action_advanced_mode);
- MenuItem menuItemDestroyRoom = menu.findItem(R.id.action_destroy_room);
+ public boolean onPrepareOptionsMenu(final Menu menu) {
+ final MenuItem menuItemSaveBookmark = menu.findItem(R.id.action_save_as_bookmark);
+ final MenuItem menuItemAdvancedMode = menu.findItem(R.id.action_advanced_mode);
+ final MenuItem menuItemDestroyRoom = menu.findItem(R.id.action_destroy_room);
menuItemAdvancedMode.setChecked(mAdvancedMode);
if (mConversation == null) {
return true;
}
- if (mConversation.getBookmark() != null) {
- menuItemSaveBookmark.setVisible(false);
- menuItemDeleteBookmark.setVisible(true);
- } else {
- menuItemDeleteBookmark.setVisible(false);
- menuItemSaveBookmark.setVisible(true);
- }
+ menuItemSaveBookmark.setVisible(mConversation.getBookmark() == null);
menuItemDestroyRoom.setVisible(mConversation.getMucOptions().getSelf().getAffiliation().ranks(MucOptions.Affiliation.OWNER));
return true;
}
@Override
- public boolean onCreateOptionsMenu(Menu menu) {
+ public boolean onCreateOptionsMenu(final Menu menu) {
final boolean groupChat = mConversation != null && mConversation.isPrivateAndNonAnonymous();
getMenuInflater().inflate(R.menu.muc_details, menu);
final MenuItem share = menu.findItem(R.id.action_share);
@@ -460,14 +450,6 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
xmppConnectionService.saveConversationAsBookmark(mConversation, mConversation.getMucOptions().getName());
}
- protected void deleteBookmark() {
- final Account account = mConversation.getAccount();
- final Bookmark bookmark = mConversation.getBookmark();
- bookmark.setConversation(null);
- xmppConnectionService.deleteBookmark(account, bookmark);
- updateView();
- }
-
protected void destroyRoom() {
final boolean groupChat = mConversation != null && mConversation.isPrivateAndNonAnonymous();
AlertDialog.Builder builder = new AlertDialog.Builder(this);
@@ -483,7 +465,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
}
@Override
- void onBackendConnected() {
+ protected void onBackendConnected() {
if (mPendingConferenceInvite != null) {
mPendingConferenceInvite.execute(this);
mPendingConferenceInvite = null;
@@ -541,12 +523,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
final MucOptions mucOptions = mConversation.getMucOptions();
final User self = mucOptions.getSelf();
- String account;
- if (Config.DOMAIN_LOCK != null) {
- account = mConversation.getAccount().getJid().getEscapedLocal();
- } else {
- account = mConversation.getAccount().getJid().asBareJid().toEscapedString();
- }
+ final String account = mConversation.getAccount().getJid().asBareJid().toEscapedString();
setTitle(mucOptions.isPrivateAndNonAnonymous() ? R.string.action_muc_details : R.string.channel_details);
final Bookmark bookmark = mConversation.getBookmark();
this.binding.editMucNameButton.setVisibility((self.getAffiliation().ranks(MucOptions.Affiliation.OWNER) || mucOptions.canChangeSubject() || (bookmark != null && mConversation.getAccount().getXmppConnection().getFeatures().bookmarks2())) ? View.VISIBLE : View.GONE);
@@ -579,7 +556,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
StylingHelper.format(spannable, this.binding.mucSubject.getCurrentTextColor());
MyLinkify.addLinks(spannable, false);
this.binding.mucSubject.setText(spannable);
- this.binding.mucSubject.setTextAppearance(this, subject.length() > (hasTitle ? 128 : 196) ? R.style.TextAppearance_Conversations_Body1_Linkified : R.style.TextAppearance_Conversations_Subhead);
+ this.binding.mucSubject.setTextAppearance( subject.length() > (hasTitle ? 128 : 196) ? com.google.android.material.R.style.TextAppearance_Material3_BodyMedium : com.google.android.material.R.style.TextAppearance_Material3_BodyLarge);
this.binding.mucSubject.setAutoLinkMask(0);
this.binding.mucSubject.setVisibility(View.VISIBLE);
this.binding.mucSubject.setMovementMethod(LinkMovementMethod.getInstance());
diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
index f184358fe..a932a400b 100644
--- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
@@ -8,6 +8,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
+import android.content.res.ColorStateList;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -35,8 +36,15 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
+import androidx.core.content.ContextCompat;
+import androidx.core.view.ViewCompat;
import androidx.databinding.DataBindingUtil;
+import com.google.android.material.color.MaterialColors;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+import com.google.common.collect.ImmutableList;
+import com.google.common.primitives.Ints;
+
import org.openintents.openpgp.util.OpenPgpUtils;
import java.util.ArrayList;
@@ -47,6 +55,7 @@ import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
+import eu.siacs.conversations.AppSettings;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
@@ -57,7 +66,9 @@ import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Bookmark;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.ListItem;
+import eu.siacs.conversations.entities.Presence;
import eu.siacs.conversations.services.AbstractQuickConversationsService;
+import eu.siacs.conversations.services.QuickConversationsService;
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
import eu.siacs.conversations.ui.adapter.MediaAdapter;
@@ -74,6 +85,7 @@ import eu.siacs.conversations.utils.Emoticons;
import eu.siacs.conversations.utils.IrregularUnicodeDetector;
import eu.siacs.conversations.utils.PhoneNumberUtilWrapper;
import eu.siacs.conversations.utils.UIHelper;
+import eu.siacs.conversations.utils.XEP0392Helper;
import eu.siacs.conversations.utils.XmppUri;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
@@ -135,13 +147,13 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
private void checkContactPermissionAndShowAddDialog() {
if (hasContactsPermission()) {
showAddToPhoneBookDialog();
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ } else if (QuickConversationsService.isContactListIntegration(this) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
}
}
private boolean hasContactsPermission() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (QuickConversationsService.isContactListIntegration(this) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return checkSelfPermission(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED;
} else {
return true;
@@ -201,6 +213,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
@Override
protected void refreshUiReal() {
+ invalidateOptionsMenu();
populateView();
}
@@ -514,12 +527,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
}
binding.detailsContactjid.setText(IrregularUnicodeDetector.style(this, contact.getJid()));
- String account;
- if (Config.DOMAIN_LOCK != null) {
- account = contact.getAccount().getJid().getEscapedLocal();
- } else {
- account = contact.getAccount().getJid().asBareJid().toEscapedString();
- }
+ final String account = contact.getAccount().getJid().asBareJid().toEscapedString();
binding.detailsAccount.setText(getString(R.string.using_account, account));
AvatarWorkerTask.loadAvatar(contact, binding.detailsContactBadge, R.dimen.avatar_on_details_screen_size);
binding.detailsContactBadge.setOnClickListener(this::onBadgeClick);
@@ -581,7 +589,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
TextView keyType = view.findViewById(R.id.key_type);
keyType.setText(R.string.openpgp_key_id);
if ("pgp".equals(messageFingerprint)) {
- keyType.setTextAppearance(this, R.style.TextAppearance_Conversations_Caption_Highlight);
+ keyType.setTextColor(MaterialColors.getColor(keyType, com.google.android.material.R.attr.colorPrimaryVariant));
}
key.setText(OpenPgpUtils.convertKeyIdToHex(contact.getPgpKeyId()));
final OnClickListener openKey = v -> launchOpenKeyChain(contact.getPgpKeyId());
@@ -592,33 +600,77 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
}
binding.keysWrapper.setVisibility(hasKeys ? View.VISIBLE : View.GONE);
- List tagList = contact.getTags(this);
- if (tagList.size() == 0 || !this.showDynamicTags) {
+ final List tagList = contact.getTags(this);
+ final boolean hasMetaTags = contact.isBlocked() || contact.getShownStatus() != Presence.Status.OFFLINE;
+ if ((tagList.isEmpty() && !hasMetaTags) || !this.showDynamicTags) {
binding.tags.setVisibility(View.GONE);
} else {
binding.tags.setVisibility(View.VISIBLE);
- binding.tags.removeAllViewsInLayout();
+ binding.tags.removeViews(1, binding.tags.getChildCount() - 1);
+ final ImmutableList.Builder viewIdBuilder = new ImmutableList.Builder<>();
for (final ListItem.Tag tag : tagList) {
+ final String name = tag.getName();
final TextView tv = (TextView) inflater.inflate(R.layout.list_item_tag, binding.tags, false);
- tv.setText(tag.getName());
- tv.setBackgroundColor(tag.getColor());
+ tv.setText(name);
+ tv.setBackgroundTintList(ColorStateList.valueOf(MaterialColors.harmonizeWithPrimary(this,XEP0392Helper.rgbFromNick(name))));
+ final int id = ViewCompat.generateViewId();
+ tv.setId(id);
+ viewIdBuilder.add(id);
binding.tags.addView(tv);
}
+ if (contact.isBlocked()) {
+ final TextView tv =
+ (TextView)
+ inflater.inflate(
+ R.layout.list_item_tag, binding.tags, false);
+ tv.setText(R.string.blocked);
+ tv.setBackgroundTintList(ColorStateList.valueOf(MaterialColors.harmonizeWithPrimary(tv.getContext(), ContextCompat.getColor(tv.getContext(),R.color.gray_800))));
+ final int id = ViewCompat.generateViewId();
+ tv.setId(id);
+ viewIdBuilder.add(id);
+ binding.tags.addView(tv);
+ } else {
+ final Presence.Status status = contact.getShownStatus();
+ if (status != Presence.Status.OFFLINE) {
+ final TextView tv =
+ (TextView)
+ inflater.inflate(
+ R.layout.list_item_tag, binding.tags, false);
+ UIHelper.setStatus(tv, status);
+ final int id = ViewCompat.generateViewId();
+ tv.setId(id);
+ viewIdBuilder.add(id);
+ binding.tags.addView(tv);
+ }
+ }
+ binding.flowWidget.setReferencedIds(Ints.toArray(viewIdBuilder.build()));
}
}
- private void onBadgeClick(View view) {
- final Uri systemAccount = contact.getSystemAccount();
- if (systemAccount == null) {
- checkContactPermissionAndShowAddDialog();
- } else {
- final Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setData(systemAccount);
- try {
- startActivity(intent);
- } catch (final ActivityNotFoundException e) {
- Toast.makeText(this, R.string.no_application_found_to_view_contact, Toast.LENGTH_SHORT).show();
+ private void onBadgeClick(final View view) {
+ if (QuickConversationsService.isContactListIntegration(this)) {
+ final Uri systemAccount = contact.getSystemAccount();
+ if (systemAccount == null) {
+ checkContactPermissionAndShowAddDialog();
+ } else {
+ final Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(systemAccount);
+ try {
+ startActivity(intent);
+ } catch (final ActivityNotFoundException e) {
+ Toast.makeText(
+ this,
+ R.string.no_application_found_to_view_contact,
+ Toast.LENGTH_SHORT)
+ .show();
+ }
}
+ } else {
+ Toast.makeText(
+ this,
+ R.string.contact_list_integration_not_available,
+ Toast.LENGTH_SHORT)
+ .show();
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
index 2d9fe91ae..da5231357 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
@@ -16,10 +16,4 @@ public class ConversationActivity extends AppCompatActivity {
startActivity(new Intent(this, ConversationsActivity.class));
finish();
}
-
- @Override
- protected void onResume(){
- super.onResume();
- SettingsUtils.applyScreenshotPreventionSetting(this);
- }
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
index 747e2f48c..12fcbfc52 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
@@ -27,6 +27,7 @@ import android.content.IntentSender.SendIntentException;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
+import android.content.res.ColorStateList;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
@@ -120,6 +121,7 @@ import eu.siacs.conversations.entities.TransferablePlaceholder;
import eu.siacs.conversations.http.HttpDownloadConnection;
import eu.siacs.conversations.medialib.activities.EditActivity;
import eu.siacs.conversations.persistance.FileBackend;
+import eu.siacs.conversations.services.CallIntegrationConnectionService;
import eu.siacs.conversations.services.MessageArchiveService;
import eu.siacs.conversations.services.QuickConversationsService;
import eu.siacs.conversations.services.XmppConnectionService;
@@ -167,6 +169,19 @@ import eu.siacs.conversations.xmpp.jingle.Media;
import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession;
import eu.siacs.conversations.xmpp.jingle.RtpCapability;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
+import eu.siacs.conversations.xmpp.jingle.RtpEndUserState;
+
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicBoolean;
public class ConversationFragment extends XmppFragment
implements EditMessage.KeyboardListener,
@@ -1278,7 +1293,7 @@ public class ConversationFragment extends XmppFragment
};
if (conversation == null
|| conversation.getMode() == Conversation.MODE_MULTI
- || Attachment.canBeSendInband(attachments)
+ || Attachment.canBeSendInBand(attachments)
|| (conversation.getAccount().httpUploadAvailable()
&& FileBackend.allFilesUnderSize(
getActivity(), attachments, getMaxHttpUploadSize(conversation)))) {
@@ -2055,21 +2070,27 @@ public class ConversationFragment extends XmppFragment
if (ongoingRtpSession.isPresent()) {
final OngoingRtpSession id = ongoingRtpSession.get();
final Intent intent = new Intent(activity, RtpSessionActivity.class);
+ intent.setAction(Intent.ACTION_VIEW);
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);
+ if (id instanceof AbstractJingleConnection) {
intent.putExtra(RtpSessionActivity.EXTRA_SESSION_ID, id.getSessionId());
- } else if (id instanceof JingleConnectionManager.RtpSessionProposal) {
- if (((JingleConnectionManager.RtpSessionProposal) id).media.contains(Media.VIDEO)) {
- intent.setAction(RtpSessionActivity.ACTION_MAKE_VIDEO_CALL);
+ startActivity(intent);
+ } else if (id instanceof JingleConnectionManager.RtpSessionProposal proposal) {
+ if (Media.audioOnly(proposal.media)) {
+ intent.putExtra(
+ RtpSessionActivity.EXTRA_LAST_ACTION,
+ RtpSessionActivity.ACTION_MAKE_VOICE_CALL);
} else {
- intent.setAction(RtpSessionActivity.ACTION_MAKE_VOICE_CALL);
+ intent.putExtra(
+ RtpSessionActivity.EXTRA_LAST_ACTION,
+ RtpSessionActivity.ACTION_MAKE_VIDEO_CALL);
}
+ intent.putExtra(RtpSessionActivity.EXTRA_PROPOSED_SESSION_ID, proposal.sessionId);
+ activity.startActivity(intent);
}
- activity.startActivity(intent);
}
}
@@ -2132,7 +2153,7 @@ public class ConversationFragment extends XmppFragment
activity.xmppConnectionService.updateAccount(account);
}
final Contact contact = conversation.getContact();
- if (RtpCapability.jmiSupport(contact)) {
+ if (Config.USE_JINGLE_MESSAGE_INIT && RtpCapability.jmiSupport(contact)) {
triggerRtpSession(contact.getAccount(), contact.getJid().asBareJid(), action);
} else {
final RtpCapability.Capability capability;
@@ -2152,13 +2173,7 @@ public class ConversationFragment extends XmppFragment
}
private void triggerRtpSession(final Account account, final Jid with, final String action) {
- final Intent intent = new Intent(activity, RtpSessionActivity.class);
- intent.setAction(action);
- intent.putExtra(RtpSessionActivity.EXTRA_ACCOUNT, account.getJid().toEscapedString());
- intent.putExtra(RtpSessionActivity.EXTRA_WITH, with.toEscapedString());
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK);
- startActivity(intent);
+ CallIntegrationConnectionService.placeCall(activity.xmppConnectionService, account,with,RtpSessionActivity.actionToMedia(action));
}
private void handleAttachmentSelection(MenuItem item) {
@@ -2486,26 +2501,22 @@ public class ConversationFragment extends XmppFragment
}
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 ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU || Config.ONLY_INTERNAL_STORAGE) && permission.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
- continue;
- }
- if (activity.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
- missingPermissions.add(permission);
- }
+ final List missingPermissions = new ArrayList<>();
+ for (String permission : permissions) {
+ if ((Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU || Config.ONLY_INTERNAL_STORAGE) && permission.equals(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
+ continue;
}
- if (missingPermissions.size() == 0) {
- return true;
- } else {
- requestPermissions(
- missingPermissions.toArray(new String[0]),
- requestCode);
- return false;
+ if (activity.checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
+ missingPermissions.add(permission);
}
- } else {
+ }
+ if (missingPermissions.size() == 0) {
return true;
+ } else {
+ requestPermissions(
+ missingPermissions.toArray(new String[0]),
+ requestCode);
+ return false;
}
}
@@ -2843,7 +2854,7 @@ public class ConversationFragment extends XmppFragment
}
@Override
- public void onSaveInstanceState(@NotNull Bundle outState) {
+ public void onSaveInstanceState(@NonNull Bundle outState) {
super.onSaveInstanceState(outState);
if (conversation != null) {
outState.putString(STATE_CONVERSATION_UUID, conversation.getUuid());
@@ -3273,10 +3284,10 @@ public class ConversationFragment extends XmppFragment
final Iterator iterator = uris.iterator();
while (iterator.hasNext()) {
final Uri uri = iterator.next();
- if (FileBackend.weOwnFile(uri)) {
+ if (FileBackend.dangerousFile(uri)) {
iterator.remove();
Toast.makeText(
- getActivity(),
+ requireActivity(),
R.string.security_violation_not_attaching_file,
Toast.LENGTH_SHORT)
.show();
@@ -3909,9 +3920,8 @@ public class ConversationFragment extends XmppFragment
});
}
- public void showNoPGPKeyDialog(boolean plural, DialogInterface.OnClickListener listener) {
- AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
- builder.setIconAttribute(android.R.attr.alertDialogIcon);
+ public void showNoPGPKeyDialog(final boolean plural, final DialogInterface.OnClickListener listener) {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity());
if (plural) {
builder.setTitle(getString(R.string.no_pgp_keys));
builder.setMessage(getText(R.string.contacts_have_no_pgp_keys));
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java
index bb76ea271..18d9313ca 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java
@@ -61,6 +61,8 @@ import androidx.appcompat.app.AlertDialog;
import androidx.core.app.ActivityCompat;
import androidx.databinding.DataBindingUtil;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
import org.openintents.openpgp.util.OpenPgpApi;
import java.util.Arrays;
@@ -74,7 +76,6 @@ import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.OmemoSetting;
import eu.siacs.conversations.databinding.ActivityConversationsBinding;
-import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Conversational;
@@ -84,11 +85,11 @@ import eu.siacs.conversations.ui.interfaces.OnConversationArchived;
import eu.siacs.conversations.ui.interfaces.OnConversationRead;
import eu.siacs.conversations.ui.interfaces.OnConversationSelected;
import eu.siacs.conversations.ui.interfaces.OnConversationsListItemUpdated;
-import eu.siacs.conversations.ui.util.ActionBarUtil;
import eu.siacs.conversations.ui.util.ActivityResult;
import eu.siacs.conversations.ui.util.ConversationMenuConfigurator;
import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
import eu.siacs.conversations.ui.util.PendingItem;
+import eu.siacs.conversations.ui.util.ToolbarUtils;
import eu.siacs.conversations.utils.ExceptionHelper;
import eu.siacs.conversations.utils.PhoneNumberUtilWrapper;
import eu.siacs.conversations.utils.SignupUtils;
@@ -153,7 +154,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
}
@Override
- void onBackendConnected() {
+ protected void onBackendConnected() {
if (performRedirectIfNecessary(true)) {
return;
}
@@ -236,10 +237,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
}
private boolean openBatteryOptimizationDialogIfNeeded() {
- if (isOptimizingBattery()
- && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M
- && getPreferences().getBoolean(getBatteryOptimizationPreferenceKey(), true)) {
- final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ if (isOptimizingBattery() && getPreferences().getBoolean(getBatteryOptimizationPreferenceKey(), true)) {
+ final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(R.string.battery_optimizations_enabled);
builder.setMessage(getString(R.string.battery_optimizations_enabled_dialog, getString(R.string.app_name)));
builder.setPositiveButton(R.string.next, (dialog, which) -> {
@@ -485,9 +484,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
conversationFragment.reInit(conversation, extras == null ? new Bundle() : extras);
if (mainNeedsRefresh) {
refreshFragment(R.id.main_fragment);
- } else {
- invalidateActionBarTitle();
}
+ invalidateActionBarTitle();
}
private static void executePendingTransactions(final FragmentManager fragmentManager) {
@@ -602,7 +600,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
}
@Override
- protected void onStart() {
+ public void onStart() {
super.onStart();
final int theme = findTheme();
if (this.mTheme != theme) {
@@ -686,8 +684,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
}
final FragmentManager fragmentManager = getFragmentManager();
final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment);
- if (mainFragment instanceof ConversationFragment) {
- final Conversation conversation = ((ConversationFragment) mainFragment).getConversation();
+ if (mainFragment instanceof ConversationFragment conversationFragment) {
+ final Conversation conversation = conversationFragment.getConversation();
if (conversation != null) {
if (conversation.getNextCounterpart() != null) {
actionBar.setTitle(getString(R.string.muc_private_conversation_title, conversation.getNextCounterpart().getResource(), conversation.getName()));
@@ -695,16 +693,26 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
actionBar.setTitle(conversation.getName());
}
actionBar.setDisplayHomeAsUpEnabled(true);
- ActionBarUtil.setActionBarOnClickListener(
+ ToolbarUtils.setActionBarOnClickListener(
binding.toolbar,
(v) -> openConversationDetails(conversation)
);
return;
}
}
- actionBar.setTitle(R.string.app_name);
+ final Fragment secondaryFragment = fragmentManager.findFragmentById(R.id.secondary_fragment);
+ if (secondaryFragment instanceof ConversationFragment conversationFragment) {
+ final Conversation conversation = conversationFragment.getConversation();
+ if (conversation != null) {
+ actionBar.setTitle(conversation.getName());
+ } else {
+ actionBar.setTitle(R.string.app_name);
+ }
+ } else {
+ actionBar.setTitle(R.string.app_name);
+ }
actionBar.setDisplayHomeAsUpEnabled(false);
- ActionBarUtil.resetActionBarOnClickListeners(binding.toolbar);
+ ToolbarUtils.resetActionBarOnClickListeners(binding.toolbar);
}
private void openConversationDetails(final Conversation conversation) {
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java
index 2527167aa..89f121de1 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationsOverviewFragment.java
@@ -64,6 +64,7 @@ import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
+import eu.siacs.conversations.BuildConfig;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.FragmentConversationsOverviewBinding;
@@ -75,6 +76,7 @@ import eu.siacs.conversations.entities.Conversational;
import eu.siacs.conversations.entities.ListItem;
import eu.siacs.conversations.entities.Presence;
import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.services.QuickConversationsService;
import eu.siacs.conversations.ui.adapter.ConversationAdapter;
import eu.siacs.conversations.ui.interfaces.OnConversationArchived;
import eu.siacs.conversations.ui.interfaces.OnConversationSelected;
@@ -168,7 +170,7 @@ public class ConversationsOverviewFragment extends XmppFragment {
title = R.string.title_undo_swipe_out_channel;
}
} else {
- title = R.string.title_undo_swipe_out_conversation;
+ title = R.string.title_undo_swipe_out_chat;
}
final Snackbar snackbar = Snackbar.make(binding.list, title, 5000)
@@ -329,6 +331,11 @@ public class ConversationsOverviewFragment extends XmppFragment {
MenuItem noteToSelf = menu.findItem(R.id.action_note_to_self);
easyOnboardInvite.setVisible(EasyOnboardingInvite.anyHasSupport(activity == null ? null : activity.xmppConnectionService));
+ final MenuItem privacyPolicyMenuItem = menu.findItem(R.id.action_privacy_policy);
+ privacyPolicyMenuItem.setVisible(
+ BuildConfig.PRIVACY_POLICY != null
+ && QuickConversationsService.isPlayStoreFlavor());
+
if (activity == null || activity.xmppConnectionService == null || activity.xmppConnectionService.getAccounts().size() != 1) {
noteToSelf.setVisible(false);
}
@@ -460,7 +467,7 @@ public class ConversationsOverviewFragment extends XmppFragment {
private void selectAccountToStartEasyInvite() {
final List accounts = EasyOnboardingInvite.getSupportingAccounts(activity.xmppConnectionService);
- if (accounts.size() == 0) {
+ if (accounts.isEmpty()) {
//This can technically happen if opening the menu item races with accounts reconnecting or something
Toast.makeText(getActivity(),R.string.no_active_accounts_support_this, Toast.LENGTH_LONG).show();
} else if (accounts.size() == 1) {
diff --git a/src/main/java/eu/siacs/conversations/ui/CreatePrivateGroupChatDialog.java b/src/main/java/eu/siacs/conversations/ui/CreatePrivateGroupChatDialog.java
index 6e4098264..ae3406a2d 100644
--- a/src/main/java/eu/siacs/conversations/ui/CreatePrivateGroupChatDialog.java
+++ b/src/main/java/eu/siacs/conversations/ui/CreatePrivateGroupChatDialog.java
@@ -3,18 +3,20 @@ package eu.siacs.conversations.ui;
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
-import android.widget.Spinner;
+import android.widget.AutoCompleteTextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.DialogFragment;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
import java.util.ArrayList;
import java.util.List;
import eu.siacs.conversations.R;
-import eu.siacs.conversations.databinding.CreateConferenceDialogBinding;
+import eu.siacs.conversations.databinding.DialogCreateConferenceBinding;
import eu.siacs.conversations.ui.util.DelayedHintHelper;
public class CreatePrivateGroupChatDialog extends DialogFragment {
@@ -39,9 +41,9 @@ public class CreatePrivateGroupChatDialog extends DialogFragment {
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
- final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity());
builder.setTitle(R.string.create_private_group_chat);
- CreateConferenceDialogBinding binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.create_conference_dialog, null, false);
+ final DialogCreateConferenceBinding binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.dialog_create_conference, null, false);
ArrayList mActivatedAccounts = getArguments().getStringArrayList(ACCOUNTS_LIST_KEY);
StartConversationActivity.populateAccountSpinner(getActivity(), mActivatedAccounts, binding.account);
builder.setView(binding.getRoot());
@@ -57,7 +59,7 @@ public class CreatePrivateGroupChatDialog extends DialogFragment {
public interface CreateConferenceDialogListener {
- void onCreateDialogPositiveClick(Spinner spinner, String subject);
+ void onCreateDialogPositiveClick(AutoCompleteTextView spinner, String subject);
}
@Override
diff --git a/src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java b/src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java
index 8f5e2e6d2..b20db451d 100644
--- a/src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java
+++ b/src/main/java/eu/siacs/conversations/ui/CreatePublicChannelDialog.java
@@ -17,13 +17,14 @@ import androidx.appcompat.app.AlertDialog;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.DialogFragment;
-import java.security.SecureRandom;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import eu.siacs.conversations.R;
-import eu.siacs.conversations.databinding.CreatePublicChannelDialogBinding;
+import eu.siacs.conversations.databinding.DialogCreatePublicChannelBinding;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
@@ -44,7 +45,7 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke
private boolean nameEntered = false;
private boolean skipTetxWatcher = false;
- public static CreatePublicChannelDialog newInstance(List accounts) {
+ public static CreatePublicChannelDialog newInstance(final List accounts) {
CreatePublicChannelDialog dialog = new CreatePublicChannelDialog();
Bundle bundle = new Bundle();
bundle.putStringArrayList(ACCOUNTS_LIST_KEY, (ArrayList) accounts);
@@ -63,9 +64,9 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke
public Dialog onCreateDialog(Bundle savedInstanceState) {
jidWasModified = savedInstanceState != null && savedInstanceState.getBoolean("jid_was_modified_false", false);
nameEntered = savedInstanceState != null && savedInstanceState.getBoolean("name_entered", false);
- final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity());
builder.setTitle(R.string.create_public_channel);
- final CreatePublicChannelDialogBinding binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.create_public_channel_dialog, null, false);
+ final DialogCreatePublicChannelBinding binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.dialog_create_public_channel, null, false);
binding.account.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView> parent, View view, int position, long id) {
@@ -107,7 +108,7 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke
builder.setPositiveButton(nameEntered ? R.string.create : R.string.next, null);
builder.setNegativeButton(nameEntered ? R.string.back : R.string.cancel, null);
DelayedHintHelper.setHint(R.string.channel_bare_jid_example, binding.jid);
- this.knownHostsAdapter = new KnownHostsAdapter(getActivity(), R.layout.simple_list_item);
+ this.knownHostsAdapter = new KnownHostsAdapter(getActivity(), R.layout.item_autocomplete);
binding.jid.setAdapter(knownHostsAdapter);
final AlertDialog dialog = builder.create();
binding.groupChatName.setOnEditorActionListener((v, actionId, event) -> {
@@ -121,7 +122,7 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke
return dialog;
}
- private void updateJidSuggestion(CreatePublicChannelDialogBinding binding) {
+ private void updateJidSuggestion(final DialogCreatePublicChannelBinding binding) {
if (jidWasModified) {
return;
}
@@ -138,7 +139,7 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke
super.onSaveInstanceState(outState);
}
- private static String getJidSuggestion(CreatePublicChannelDialogBinding binding) {
+ private static String getJidSuggestion(final DialogCreatePublicChannelBinding binding) {
final Account account = StartConversationActivity.getSelectedAccount(binding.getRoot().getContext(), binding.account);
final XmppConnection connection = account == null ? null : account.getXmppConnection();
if (connection == null) {
@@ -169,7 +170,7 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke
return name.replaceAll("\\s+","-");
}
- private void goBack(AlertDialog dialog, CreatePublicChannelDialogBinding binding) {
+ private void goBack(AlertDialog dialog, DialogCreatePublicChannelBinding binding) {
if (nameEntered) {
nameEntered = false;
updateInputs(binding, true);
@@ -179,7 +180,7 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke
}
}
- private void submit(AlertDialog dialog, CreatePublicChannelDialogBinding binding) {
+ private void submit(AlertDialog dialog, DialogCreatePublicChannelBinding binding) {
final Context context = binding.getRoot().getContext();
final Editable nameText = binding.groupChatName.getText();
final String name = nameText == null ? "" : nameText.toString().trim();
@@ -227,7 +228,7 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke
}
- private void updateInputs(CreatePublicChannelDialogBinding binding, boolean requestFocus) {
+ private void updateInputs(final DialogCreatePublicChannelBinding binding, final boolean requestFocus) {
binding.xmppAddressLayout.setVisibility(nameEntered ? View.VISIBLE : View.GONE);
binding.nameLayout.setVisibility(nameEntered ? View.GONE : View.VISIBLE);
if (!requestFocus) {
@@ -265,7 +266,7 @@ public class CreatePublicChannelDialog extends DialogFragment implements OnBacke
}
@Override
- public void onAttach(Context context) {
+ public void onAttach(@NonNull Context context) {
super.onAttach(context);
try {
mListener = (CreatePublicChannelDialogListener) context;
diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
index 792df76a7..59b902d56 100644
--- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
@@ -33,19 +33,15 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
-import androidx.appcompat.app.AlertDialog.Builder;
import androidx.databinding.DataBindingUtil;
+import androidx.lifecycle.Lifecycle;
+import com.google.android.material.color.MaterialColors;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.textfield.TextInputLayout;
import com.google.common.base.CharMatcher;
-import org.openintents.openpgp.util.OpenPgpUtils;
-
-import java.util.Arrays;
-import java.util.List;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicInteger;
-
+import eu.siacs.conversations.AppSettings;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
@@ -82,8 +78,16 @@ import eu.siacs.conversations.xmpp.XmppConnection;
import eu.siacs.conversations.xmpp.XmppConnection.Features;
import eu.siacs.conversations.xmpp.forms.Data;
import eu.siacs.conversations.xmpp.pep.Avatar;
+
import okhttp3.HttpUrl;
+import org.openintents.openpgp.util.OpenPgpUtils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+
public class EditAccountActivity extends OmemoActivity implements OnAccountUpdate, OnUpdateBlocklist,
OnKeyStatusUpdated, OnCaptchaRequested, KeyChainAliasCallback, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnMamPreferencesFetched {
@@ -98,7 +102,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
private Jid jidToEdit;
private boolean mInitMode = false;
private Boolean mForceRegister = null;
- private boolean mUsernameMode = Config.DOMAIN_LOCK != null;
+ private boolean mUsernameMode = false;
private boolean mShowOptions = false;
private Account mAccount;
private final OnClickListener mCancelButtonClickListener = v -> {
@@ -609,6 +613,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
this.mSavedInstanceInit = savedInstanceState.getBoolean("initMode", false);
}
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_edit_account);
+ Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
setSupportActionBar(binding.toolbar);
binding.accountJid.addTextChangedListener(this.mTextWatcher);
binding.accountJid.setOnFocusChangeListener(this.mEditTextFocusListener);
@@ -697,13 +702,10 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
}
@Override
- protected void onStart() {
+ public void onStart() {
super.onStart();
final Intent intent = getIntent();
- final int theme = findTheme();
- if (this.mTheme != theme) {
- recreate();
- } else if (intent != null) {
+ if (intent != null) {
try {
this.jidToEdit = Jid.ofEscaped(intent.getStringExtra("jid"));
} catch (final IllegalArgumentException | NullPointerException ignored) {
@@ -758,7 +760,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
}
private void displayVerificationWarningDialog(final XmppUri xmppUri) {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(R.string.verify_omemo_keys);
View view = getLayoutInflater().inflate(R.layout.dialog_verify_fingerprints, null);
final CheckBox isTrustedSource = view.findViewById(R.id.trusted_source);
@@ -773,7 +775,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
}
});
builder.setNegativeButton(R.string.cancel, (dialog, which) -> finish());
- AlertDialog dialog = builder.create();
+ final var dialog = builder.create();
dialog.setCanceledOnTouchOutside(false);
dialog.setOnCancelListener(d -> finish());
dialog.show();
@@ -835,7 +837,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
this.binding.accountJidLayout.setHint(getString(R.string.username_hint));
} else {
final KnownHostsAdapter mKnownHostsAdapter = new KnownHostsAdapter(this,
- R.layout.simple_list_item,
+ R.layout.item_autocomplete,
xmppConnectionService.getKnownHosts());
this.binding.accountJid.setAdapter(mKnownHostsAdapter);
}
@@ -853,7 +855,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
if (mAccount != null && mAccount.getJid().getDomain() != null) {
return mAccount.getServer();
} else {
- return Config.DOMAIN_LOCK;
+ return null;
}
}
@@ -939,8 +941,8 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
private void changePresence() {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
- boolean manualStatus = sharedPreferences.getBoolean(SettingsActivity.MANUALLY_CHANGE_PRESENCE, getResources().getBoolean(R.bool.manually_change_presence));
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ boolean manualStatus = sharedPreferences.getBoolean(AppSettings.MANUALLY_CHANGE_PRESENCE, getResources().getBoolean(R.bool.manually_change_presence));
+ final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
final DialogPresenceBinding binding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.dialog_presence, null, false);
String current = mAccount.getPresenceStatusMessage();
if (current != null && !current.trim().isEmpty()) {
@@ -949,7 +951,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
setAvailabilityRadioButton(mAccount.getPresenceStatus(), binding);
binding.show.setVisibility(manualStatus ? View.VISIBLE : View.GONE);
List templates = xmppConnectionService.getPresenceTemplates(mAccount);
- PresenceTemplateAdapter presenceTemplateAdapter = new PresenceTemplateAdapter(this, R.layout.simple_list_item, templates);
+ PresenceTemplateAdapter presenceTemplateAdapter = new PresenceTemplateAdapter(this, R.layout.item_autocomplete, templates);
binding.statusMessage.setAdapter(presenceTemplateAdapter);
binding.statusMessage.setOnItemClickListener((parent, view, position, id) -> {
PresenceTemplate template = (PresenceTemplate) parent.getItemAtPosition(position);
@@ -1144,7 +1146,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
this.binding.pgpFingerprint.setText(OpenPgpUtils.convertKeyIdToHex(pgpKeyId));
this.binding.pgpFingerprint.setOnClickListener(openPgp);
if ("pgp".equals(messageFingerprint)) {
- this.binding.pgpFingerprintDesc.setTextAppearance(this, R.style.TextAppearance_Conversations_Caption_Highlight);
+ this.binding.pgpFingerprintDesc.setTextColor(MaterialColors.getColor(binding.pgpFingerprintDesc, com.google.android.material.R.attr.colorPrimaryVariant));
}
this.binding.pgpFingerprintDesc.setOnClickListener(openPgp);
this.binding.actionDeletePgp.setOnClickListener(delete);
@@ -1155,10 +1157,10 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
if (ownAxolotlFingerprint != null && Config.supportOmemo()) {
this.binding.axolotlFingerprintBox.setVisibility(View.VISIBLE);
if (ownAxolotlFingerprint.equals(messageFingerprint)) {
- this.binding.ownFingerprintDesc.setTextAppearance(this, R.style.TextAppearance_Conversations_Caption_Highlight);
+ this.binding.ownFingerprintDesc.setTextColor(MaterialColors.getColor(binding.ownFingerprintDesc, com.google.android.material.R.attr.colorPrimaryVariant));
this.binding.ownFingerprintDesc.setText(R.string.omemo_fingerprint_selected_message);
} else {
- this.binding.ownFingerprintDesc.setTextAppearance(this, R.style.TextAppearance_Conversations_Caption);
+ this.binding.ownFingerprintDesc.setTextColor(MaterialColors.getColor(binding.ownFingerprintDesc, com.google.android.material.R.attr.colorOnSurface));
this.binding.ownFingerprintDesc.setText(R.string.omemo_fingerprint);
}
this.binding.axolotlFingerprint.setText(CryptoHelper.prettifyFingerprint(ownAxolotlFingerprint.substring(2)));
@@ -1222,10 +1224,10 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
private void updateDisplayName(String displayName) {
if (TextUtils.isEmpty(displayName)) {
this.binding.yourName.setText(R.string.no_name_set_instructions);
- this.binding.yourName.setTextAppearance(this, R.style.TextAppearance_Conversations_Body1_Tertiary);
+ this.binding.yourName.setTextColor(MaterialColors.getColor(binding.yourName, com.google.android.material.R.attr.colorOnSurfaceVariant));
} else {
this.binding.yourName.setText(displayName);
- this.binding.yourName.setTextAppearance(this, R.style.TextAppearance_Conversations_Body1);
+ this.binding.yourName.setTextColor(MaterialColors.getColor(binding.yourName, com.google.android.material.R.attr.colorOnSurfaceVariant));
}
}
@@ -1249,7 +1251,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
}
private void showDeletePgpDialog() {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(R.string.unpublish_pgp);
builder.setMessage(R.string.unpublish_pgp_message);
builder.setNegativeButton(R.string.cancel, null);
@@ -1279,7 +1281,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
Toast.makeText(EditAccountActivity.this, getString(R.string.device_does_not_support_data_saver, getString(R.string.app_name)), Toast.LENGTH_SHORT).show();
}
});
- } else if (showBatteryWarning && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
+ } else if (showBatteryWarning) {
this.binding.osOptimizationDisable.setText(R.string.disable);
this.binding.osOptimizationHeadline.setText(R.string.battery_optimizations_enabled);
this.binding.osOptimizationBody.setText(getString(R.string.battery_optimizations_enabled_explained, getString(R.string.app_name)));
@@ -1297,7 +1299,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
}
public void showWipePepDialog() {
- Builder builder = new Builder(this);
+ final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(getString(R.string.clear_other_devices));
builder.setIconAttribute(android.R.attr.alertDialogIcon);
builder.setMessage(getString(R.string.clear_other_devices_desc));
@@ -1324,7 +1326,11 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
if (mCaptchaDialog != null && mCaptchaDialog.isShowing()) {
mCaptchaDialog.dismiss();
}
- final Builder builder = new Builder(EditAccountActivity.this);
+ if (!getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
+ Log.d(Config.LOGTAG,"activity not running when captcha was requested");
+ return;
+ }
+ final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(EditAccountActivity.this);
final View view = getLayoutInflater().inflate(R.layout.captcha, null);
final ImageView imageView = view.findViewById(R.id.captcha);
final EditText input = view.findViewById(R.id.input);
@@ -1372,7 +1378,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
if (mFetchingMamPrefsToast != null) {
mFetchingMamPrefsToast.cancel();
}
- Builder builder = new Builder(EditAccountActivity.this);
+ final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(EditAccountActivity.this);
builder.setTitle(R.string.server_side_mam_prefs);
String defaultAttr = prefs.getAttribute("default");
final List defaults = Arrays.asList("never", "roster", "always");
diff --git a/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java b/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java
index f0793f743..f8ecfe9d6 100644
--- a/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java
+++ b/src/main/java/eu/siacs/conversations/ui/EnterJidDialog.java
@@ -33,9 +33,8 @@ import java.util.Map;
import io.michaelrocks.libphonenumber.android.NumberParseException;
-import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
-import eu.siacs.conversations.databinding.EnterJidDialogBinding;
+import eu.siacs.conversations.databinding.DialogEnterJidBinding;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
@@ -68,7 +67,7 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected
private KnownHostsAdapter knownHostsAdapter;
private Collection whitelistedDomains = Collections.emptyList();
- private EnterJidDialogBinding binding;
+ private DialogEnterJidBinding binding;
private AlertDialog dialog;
private SanityCheck sanityCheckJid = SanityCheck.NO;
@@ -82,7 +81,7 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected
}
public static EnterJidDialog newInstance(
- final List activatedAccounts,
+ final ArrayList activatedAccounts,
final String title,
final String positiveButton,
final String secondaryButton,
@@ -133,7 +132,7 @@ public class EnterJidDialog extends DialogFragment implements OnBackendConnected
this.knownHostsAdapter = new KnownHostsAdapter(getActivity(), R.layout.simple_list_item);
binding.jid.setAdapter(this.knownHostsAdapter);
binding.jid.addTextChangedListener(this);
- String prefilledJid = getArguments().getString(PREFILLED_JID_KEY);
+ final String prefilledJid = arguments.getString(PREFILLED_JID_KEY);
if (prefilledJid != null) {
binding.jid.append(prefilledJid);
if (!getArguments().getBoolean(ALLOW_EDIT_JID_KEY)) {
diff --git a/src/main/java/eu/siacs/conversations/ui/ExtendedFabSizeChanger.java b/src/main/java/eu/siacs/conversations/ui/ExtendedFabSizeChanger.java
new file mode 100644
index 000000000..6c21f1a2d
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/ui/ExtendedFabSizeChanger.java
@@ -0,0 +1,29 @@
+package eu.siacs.conversations.ui;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+import com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton;
+
+public class ExtendedFabSizeChanger extends RecyclerView.OnScrollListener {
+
+ private final ExtendedFloatingActionButton extendedFloatingActionButton;
+
+ private ExtendedFabSizeChanger(
+ final ExtendedFloatingActionButton extendedFloatingActionButton) {
+ this.extendedFloatingActionButton = extendedFloatingActionButton;
+ }
+
+ @Override
+ public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
+ super.onScrolled(recyclerView, dx, dy);
+ if (RecyclerViews.findFirstVisibleItemPosition(recyclerView) > 0) {
+ extendedFloatingActionButton.shrink();
+ } else {
+ extendedFloatingActionButton.extend();
+ }
+ }
+
+ public static RecyclerView.OnScrollListener of(final ExtendedFloatingActionButton fab) {
+ return new ExtendedFabSizeChanger(fab);
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/ui/JoinConferenceDialog.java b/src/main/java/eu/siacs/conversations/ui/JoinConferenceDialog.java
index e17aab3a4..7ed3fbd9d 100644
--- a/src/main/java/eu/siacs/conversations/ui/JoinConferenceDialog.java
+++ b/src/main/java/eu/siacs/conversations/ui/JoinConferenceDialog.java
@@ -13,6 +13,7 @@ import androidx.appcompat.app.AlertDialog;
import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.DialogFragment;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.textfield.TextInputLayout;
import java.util.ArrayList;
@@ -50,11 +51,11 @@ public class JoinConferenceDialog extends DialogFragment implements OnBackendCon
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
- final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+ final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(requireActivity());
builder.setTitle(R.string.join_public_channel);
- DialogJoinConferenceBinding binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.dialog_join_conference, null, false);
+ final DialogJoinConferenceBinding binding = DataBindingUtil.inflate(getActivity().getLayoutInflater(), R.layout.dialog_join_conference, null, false);
DelayedHintHelper.setHint(R.string.channel_full_jid_example, binding.jid);
- this.knownHostsAdapter = new KnownHostsAdapter(getActivity(), R.layout.simple_list_item);
+ this.knownHostsAdapter = new KnownHostsAdapter(getActivity(), R.layout.item_autocomplete);
binding.jid.setAdapter(knownHostsAdapter);
String prefilledJid = getArguments().getString(PREFILLED_JID_KEY);
if (prefilledJid != null) {
@@ -117,6 +118,6 @@ public class JoinConferenceDialog extends DialogFragment implements OnBackendCon
}
public interface JoinConferenceDialogListener {
- void onJoinDialogPositiveClick(Dialog dialog, Spinner spinner, TextInputLayout jidLayout, AutoCompleteTextView jid, boolean isBookmarkChecked);
+ void onJoinDialogPositiveClick(Dialog dialog, AutoCompleteTextView spinner, TextInputLayout jidLayout, AutoCompleteTextView jid, boolean isBookmarkChecked);
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/LocationActivity.java b/src/main/java/eu/siacs/conversations/ui/LocationActivity.java
index 49ca027c0..141874b48 100644
--- a/src/main/java/eu/siacs/conversations/ui/LocationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/LocationActivity.java
@@ -40,7 +40,6 @@ import eu.siacs.conversations.ui.util.LocationHelper;
import eu.siacs.conversations.ui.widget.Marker;
import eu.siacs.conversations.ui.widget.MyLocation;
import eu.siacs.conversations.ui.util.SettingsUtils;
-import eu.siacs.conversations.utils.ThemeHelper;
public abstract class LocationActivity extends ActionBarActivity implements LocationListener {
protected LocationManager locationManager;
@@ -78,7 +77,6 @@ public abstract class LocationActivity extends ActionBarActivity implements Loca
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final Context ctx = getApplicationContext();
- setTheme(ThemeHelper.find(this));
Integer override = ThemeHelper.findThemeOverrideStyle(this);
if (override != null) {
@@ -95,7 +93,7 @@ public abstract class LocationActivity extends ActionBarActivity implements Loca
// Ask for location permissions if location services are enabled and we're
// just starting the activity (we don't want to keep pestering them on every
// screen rotation or if there's no point because it's disabled anyways).
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && savedInstanceState == null) {
+ if (savedInstanceState == null) {
requestPermissions(REQUEST_CODE_CREATE);
}
@@ -229,7 +227,6 @@ public abstract class LocationActivity extends ActionBarActivity implements Loca
@Override
protected void onResume() {
super.onResume();
- SettingsUtils.applyScreenshotPreventionSetting(this);
Configuration.getInstance().load(this, getPreferences());
map.onResume();
this.setMyLoc(null);
@@ -244,13 +241,11 @@ public abstract class LocationActivity extends ActionBarActivity implements Loca
}
}
- @TargetApi(Build.VERSION_CODES.M)
protected boolean hasLocationPermissions() {
return (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED ||
checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED);
}
- @TargetApi(Build.VERSION_CODES.M)
protected void requestPermissions(final int request_code) {
if (!hasLocationPermissions()) {
requestPermissions(
diff --git a/src/main/java/eu/siacs/conversations/ui/MediaBrowserActivity.java b/src/main/java/eu/siacs/conversations/ui/MediaBrowserActivity.java
index 3793203dd..ac5b07e77 100644
--- a/src/main/java/eu/siacs/conversations/ui/MediaBrowserActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/MediaBrowserActivity.java
@@ -29,6 +29,7 @@ public class MediaBrowserActivity extends XmppActivity implements OnMediaLoaded
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.binding = DataBindingUtil.setContentView(this,R.layout.activity_media_browser);
+ Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
setSupportActionBar(binding.toolbar);
configureActionBar(getSupportActionBar());
mMediaAdapter = new MediaAdapter(this, R.dimen.media_size);
@@ -43,7 +44,7 @@ public class MediaBrowserActivity extends XmppActivity implements OnMediaLoaded
}
@Override
- void onBackendConnected() {
+ protected void onBackendConnected() {
Intent intent = getIntent();
String account = intent == null ? null : intent.getStringExtra("account");
String jid = intent == null ? null : intent.getStringExtra("jid");
diff --git a/src/main/java/eu/siacs/conversations/ui/MemorizingActivity.java b/src/main/java/eu/siacs/conversations/ui/MemorizingActivity.java
index 56056f505..fa86cbffa 100644
--- a/src/main/java/eu/siacs/conversations/ui/MemorizingActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/MemorizingActivity.java
@@ -66,7 +66,7 @@ public class MemorizingActivity extends AppCompatActivity implements OnClickList
@Override
public void onResume() {
super.onResume();
- SettingsUtils.applyScreenshotPreventionSetting(this);
+ SettingsUtils.applyScreenshotSetting(this);
Intent i = getIntent();
decisionId = i.getIntExtra(MemorizingTrustManager.DECISION_INTENT_ID, MTMDecision.DECISION_INVALID);
diff --git a/src/main/java/eu/siacs/conversations/ui/MucUsersActivity.java b/src/main/java/eu/siacs/conversations/ui/MucUsersActivity.java
index e759ee18e..72cad4cd0 100644
--- a/src/main/java/eu/siacs/conversations/ui/MucUsersActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/MucUsersActivity.java
@@ -49,7 +49,7 @@ public class MucUsersActivity extends XmppActivity implements XmppConnectionServ
}
@Override
- void onBackendConnected() {
+ protected void onBackendConnected() {
final Intent intent = getIntent();
final String uuid = intent == null ? null : intent.getStringExtra("uuid");
if (uuid != null) {
@@ -102,8 +102,9 @@ public class MucUsersActivity extends XmppActivity implements XmppConnectionServ
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- ActivityMucUsersBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_muc_users);
+ final ActivityMucUsersBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_muc_users);
setSupportActionBar(binding.toolbar);
+ Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
configureActionBar(getSupportActionBar(), true);
this.userAdapter = new UserAdapter(getPreferences().getBoolean("advanced_muc_mode", false));
binding.list.setAdapter(this.userAdapter);
diff --git a/src/main/java/eu/siacs/conversations/ui/OmemoActivity.java b/src/main/java/eu/siacs/conversations/ui/OmemoActivity.java
index 44af0d0b2..ac7559769 100644
--- a/src/main/java/eu/siacs/conversations/ui/OmemoActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/OmemoActivity.java
@@ -11,6 +11,9 @@ import android.widget.Toast;
import androidx.appcompat.app.AlertDialog;
import androidx.databinding.DataBindingUtil;
+import com.google.android.material.color.MaterialColors;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
+
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
@@ -33,10 +36,7 @@ public abstract class OmemoActivity extends XmppActivity {
Object account = v.getTag(R.id.TAG_ACCOUNT);
Object fingerprint = v.getTag(R.id.TAG_FINGERPRINT);
Object fingerprintStatus = v.getTag(R.id.TAG_FINGERPRINT_STATUS);
- if (account != null
- && fingerprint != null
- && account instanceof Account
- && fingerprintStatus != null
+ if (account instanceof Account
&& fingerprint instanceof String
&& fingerprintStatus instanceof FingerprintStatus) {
getMenuInflater().inflate(R.menu.omemo_key_context, menu);
@@ -130,8 +130,8 @@ public abstract class OmemoActivity extends XmppActivity {
binding.tglTrust.setChecked(status.isTrusted());
if (status.isActive()) {
- binding.key.setTextAppearance(this,R.style.TextAppearance_Conversations_Fingerprint);
- binding.keyType.setTextAppearance(this,R.style.TextAppearance_Conversations_Caption);
+ binding.key.setTextColor(MaterialColors.getColor(binding.key, com.google.android.material.R.attr.colorOnSurface));
+ binding.keyType.setTextColor(MaterialColors.getColor(binding.keyType, com.google.android.material.R.attr.colorOnSurface));
if (status.isVerified()) {
binding.verifiedFingerprint.setVisibility(View.VISIBLE);
binding.verifiedFingerprint.setAlpha(1.0f);
@@ -157,8 +157,8 @@ public abstract class OmemoActivity extends XmppActivity {
toast = v -> hideToast();
}
} else {
- binding.key.setTextAppearance(this,R.style.TextAppearance_Conversations_Fingerprint_Disabled);
- binding.keyType.setTextAppearance(this,R.style.TextAppearance_Conversations_Caption_Disabled);
+ binding.key.setTextColor(MaterialColors.getColor(binding.key, com.google.android.material.R.attr.colorOnSurfaceVariant));
+ binding.keyType.setTextColor(MaterialColors.getColor(binding.keyType, com.google.android.material.R.attr.colorOnSurfaceVariant));
toast = v -> replaceToast(getString(R.string.this_device_is_no_longer_in_use), false);
if (status.isVerified()) {
binding.tglTrust.setVisibility(View.GONE);
@@ -181,7 +181,7 @@ public abstract class OmemoActivity extends XmppActivity {
binding.keyType.setVisibility(View.GONE);
}
if (highlight) {
- binding.keyType.setTextAppearance(this,R.style.TextAppearance_Conversations_Caption_Highlight);
+ binding.keyType.setTextColor(MaterialColors.getColor(binding.keyType, com.google.android.material.R.attr.colorPrimaryVariant));
binding.keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509_selected_message : R.string.omemo_fingerprint_selected_message));
} else {
binding.keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint));
@@ -191,7 +191,7 @@ public abstract class OmemoActivity extends XmppActivity {
}
public void showPurgeKeyDialog(final Account account, final String fingerprint) {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(R.string.distrust_omemo_key);
builder.setMessage(R.string.distrust_omemo_key_text);
builder.setNegativeButton(getString(R.string.cancel), null);
diff --git a/src/main/java/eu/siacs/conversations/ui/PublishGroupChatProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/PublishGroupChatProfilePictureActivity.java
index 658567aa6..8d686c36f 100644
--- a/src/main/java/eu/siacs/conversations/ui/PublishGroupChatProfilePictureActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/PublishGroupChatProfilePictureActivity.java
@@ -63,7 +63,7 @@ public class PublishGroupChatProfilePictureActivity extends XmppActivity impleme
}
@Override
- void onBackendConnected() {
+ protected void onBackendConnected() {
String uuid = pendingConversationUuid.pop();
if (uuid != null) {
this.conversation = xmppConnectionService.findConversationByUuid(uuid);
@@ -91,6 +91,7 @@ public class PublishGroupChatProfilePictureActivity extends XmppActivity impleme
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_publish_profile_picture);
+ Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
setSupportActionBar(this.binding.toolbar);
configureActionBar(getSupportActionBar());
this.binding.cancelButton.setOnClickListener((v) -> this.finish());
@@ -114,6 +115,7 @@ public class PublishGroupChatProfilePictureActivity extends XmppActivity impleme
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE) {
final CropImage.ActivityResult result = CropImage.getActivityResult(data);
if (resultCode == RESULT_OK) {
diff --git a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java
index b6822b301..2c972734c 100644
--- a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java
@@ -18,6 +18,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
+import androidx.databinding.DataBindingUtil;
import com.canhub.cropper.CropImage;
@@ -25,6 +26,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
+import eu.siacs.conversations.databinding.ActivityPublishProfilePictureBinding;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.ui.interfaces.OnAvatarPublication;
@@ -77,7 +79,6 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC
public void onAvatarPublicationFailed(int res) {
runOnUiThread(() -> {
hintOrWarning.setText(res);
- hintOrWarning.setTextAppearance(this,R.style.TextAppearance_Conversations_Body1_Warning);
hintOrWarning.setVisibility(View.VISIBLE);
publishing = false;
togglePublishButton(true, R.string.publish);
@@ -87,8 +88,12 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_publish_profile_picture);
- setSupportActionBar(findViewById(R.id.toolbar));
+
+ ActivityPublishProfilePictureBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_publish_profile_picture);
+
+ setSupportActionBar(binding.toolbar);
+
+ Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
this.avatar = findViewById(R.id.account_image);
this.cancelButton = findViewById(R.id.cancel_button);
@@ -220,7 +225,7 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC
}
@Override
- protected void onStart() {
+ public void onStart() {
super.onStart();
final Intent intent = getIntent();
this.mInitialAccountSetup = intent != null && intent.getBooleanExtra("setup", false);
@@ -261,7 +266,6 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC
if (bm == null) {
togglePublishButton(false, R.string.publish);
this.hintOrWarning.setVisibility(View.VISIBLE);
- this.hintOrWarning.setTextAppearance(this,R.style.TextAppearance_Conversations_Body1_Warning);
this.hintOrWarning.setText(R.string.error_publish_avatar_converting);
return;
}
@@ -272,7 +276,6 @@ public class PublishProfilePictureActivity extends XmppActivity implements XmppC
} else {
togglePublishButton(false, R.string.publish);
this.hintOrWarning.setVisibility(View.VISIBLE);
- this.hintOrWarning.setTextAppearance(this,R.style.TextAppearance_Conversations_Body1_Warning);
if (account.getStatus() == Account.State.ONLINE) {
this.hintOrWarning.setText(R.string.error_publish_avatar_no_server_support);
} else {
diff --git a/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java b/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java
index de2254b4d..e4fdab63a 100644
--- a/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java
@@ -25,6 +25,7 @@ import java.util.Locale;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.Set;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
@@ -72,15 +73,8 @@ public class RecordingActivity extends Activity implements View.OnClickListener
this.setFinishOnTouchOutside(false);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
-
@Override
- protected void onResume() {
- super.onResume();
- SettingsUtils.applyScreenshotPreventionSetting(this);
- }
-
- @Override
- protected void onStart() {
+ public void onStart() {
super.onStart();
if (!startRecording()) {
this.binding.shareButton.setEnabled(false);
@@ -101,21 +95,43 @@ public class RecordingActivity extends Activity implements View.OnClickListener
}
}
+ private static final Set AAC_SENSITIVE_DEVICES =
+ new ImmutableSet.Builder()
+ .add("FP4") // Fairphone 4 https://codeberg.org/monocles/monocles_chat/issues/133
+ .add("ONEPLUS A6000") // OnePlus 6 https://github.com/iNPUTmice/Conversations/issues/4329
+ .add("ONEPLUS A6003") // OnePlus 6 https://github.com/iNPUTmice/Conversations/issues/4329
+ .add("ONEPLUS A6010") // OnePlus 6T https://codeberg.org/monocles/monocles_chat/issues/133
+ .add("ONEPLUS A6013") // OnePlus 6T https://codeberg.org/monocles/monocles_chat/issues/133
+ .add("Pixel 4a") // Pixel 4a https://github.com/iNPUTmice/Conversations/issues/4223
+ .add("WP12 Pro") // Oukitel WP 12 Pro https://github.com/iNPUTmice/Conversations/issues/4223
+ .add("Volla Phone X") // Volla Phone X https://github.com/iNPUTmice/Conversations/issues/4223
+ .build();
+
private boolean startRecording() {
mRecorder = new MediaRecorder();
mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+ mRecorder.setPrivacySensitive(true);
+ }
final int outputFormat;
if (Config.USE_OPUS_VOICE_MESSAGES && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
outputFormat = MediaRecorder.OutputFormat.OGG;
mRecorder.setOutputFormat(outputFormat);
mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.OPUS);
- mRecorder.setAudioEncodingBitRate(32000);
+ mRecorder.setAudioEncodingBitRate(32_000);
} else {
outputFormat = MediaRecorder.OutputFormat.MPEG_4;
mRecorder.setOutputFormat(outputFormat);
- mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
- mRecorder.setAudioEncodingBitRate(96000);
- mRecorder.setAudioSamplingRate(22050);
+ if (AAC_SENSITIVE_DEVICES.contains(Build.MODEL) && Build.VERSION.SDK_INT <= Build.VERSION_CODES.TIRAMISU) {
+ // Changing these three settings for AAC sensitive devices for Android<=13 might lead to sporadically truncated (cut-off) voice messages.
+ mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.HE_AAC);
+ mRecorder.setAudioSamplingRate(24_000);
+ mRecorder.setAudioEncodingBitRate(28_000);
+ } else {
+ mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AAC);
+ mRecorder.setAudioSamplingRate(44_100);
+ mRecorder.setAudioEncodingBitRate(64_000);
+ }
}
setupOutputFile(outputFormat);
mRecorder.setOutputFile(mOutputFile.getAbsolutePath());
diff --git a/src/main/java/eu/siacs/conversations/ui/RecyclerViews.java b/src/main/java/eu/siacs/conversations/ui/RecyclerViews.java
new file mode 100644
index 000000000..cca9c36c0
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/ui/RecyclerViews.java
@@ -0,0 +1,29 @@
+package eu.siacs.conversations.ui;
+
+import androidx.recyclerview.widget.LinearLayoutManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+public final class RecyclerViews {
+
+ private RecyclerViews() {
+ throw new IllegalStateException("Do not instantiate me");
+ }
+
+ public static boolean scrolledToTop(final RecyclerView recyclerView) {
+ final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
+ if (layoutManager instanceof LinearLayoutManager linearLayoutManager) {
+ return linearLayoutManager.findFirstCompletelyVisibleItemPosition() == 0;
+ } else {
+ return false;
+ }
+ }
+
+ public static int findFirstVisibleItemPosition(final RecyclerView recyclerView) {
+ final RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
+ if (layoutManager instanceof LinearLayoutManager linearLayoutManager) {
+ return linearLayoutManager.findFirstVisibleItemPosition();
+ } else {
+ return RecyclerView.NO_POSITION;
+ }
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java
index 9759841df..f83d8a0be 100644
--- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java
@@ -6,17 +6,16 @@ import static java.util.Arrays.asList;
import android.Manifest;
import android.annotation.SuppressLint;
-import android.app.Activity;
import android.app.PictureInPictureParams;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.opengl.GLException;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
-import android.os.SystemClock;
import android.util.Log;
import android.util.Rational;
import android.view.KeyEvent;
@@ -57,7 +56,8 @@ import eu.siacs.conversations.databinding.ActivityRtpSessionBinding;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.Conversation;
-import eu.siacs.conversations.services.AppRTCAudioManager;
+import eu.siacs.conversations.services.CallIntegration;
+import eu.siacs.conversations.services.CallIntegrationConnectionService;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.ui.widget.DialpadView;
import eu.siacs.conversations.ui.util.AvatarWorkerTask;
@@ -71,6 +71,7 @@ import eu.siacs.conversations.xmpp.jingle.ContentAddition;
import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
import eu.siacs.conversations.xmpp.jingle.Media;
+import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession;
import eu.siacs.conversations.xmpp.jingle.RtpCapability;
import eu.siacs.conversations.xmpp.jingle.RtpEndUserState;
@@ -90,6 +91,7 @@ public class RtpSessionActivity extends XmppActivity
public static final String EXTRA_WITH = "with";
public static final String EXTRA_SESSION_ID = "session_id";
+ public static final String EXTRA_PROPOSED_SESSION_ID = "proposed_session_id";
public static final String EXTRA_LAST_REPORTED_STATE = "last_reported_state";
public static final String EXTRA_LAST_ACTION = "last_action";
public static final String ACTION_ACCEPT_CALL = "action_accept_call";
@@ -103,6 +105,7 @@ public class RtpSessionActivity extends XmppActivity
RtpEndUserState.APPLICATION_ERROR,
RtpEndUserState.SECURITY_ERROR,
RtpEndUserState.DECLINED_OR_BUSY,
+ RtpEndUserState.CONTACT_OFFLINE,
RtpEndUserState.CONNECTIVITY_ERROR,
RtpEndUserState.CONNECTIVITY_LOST_ERROR,
RtpEndUserState.RETRACTED);
@@ -124,6 +127,14 @@ public class RtpSessionActivity extends XmppActivity
RtpEndUserState.ACCEPTING_CALL,
RtpEndUserState.CONNECTING,
RtpEndUserState.RECONNECTING);
+ private static final List STATES_SHOWING_SPEAKER_CONFIGURATION =
+ new ImmutableList.Builder()
+ .add(RtpEndUserState.FINDING_DEVICE)
+ .add(RtpEndUserState.RINGING)
+ .add(RtpEndUserState.ACCEPTING_CALL)
+ .add(RtpEndUserState.CONNECTING)
+ .addAll(STATES_CONSIDERED_CONNECTED)
+ .build();
private static final String PROXIMITY_WAKE_LOCK_TAG = "conversations:in-rtp-session";
private static final int REQUEST_ACCEPT_CALL = 0x1111;
private static final int REQUEST_ACCEPT_CONTENT = 0x1112;
@@ -143,11 +154,16 @@ public class RtpSessionActivity extends XmppActivity
}
};
- private static Set actionToMedia(final String action) {
+ public static Set actionToMedia(final String action) {
if (ACTION_MAKE_VIDEO_CALL.equals(action)) {
return ImmutableSet.of(Media.AUDIO, Media.VIDEO);
- } else {
+ } else if (ACTION_MAKE_VOICE_CALL.equals(action)) {
return ImmutableSet.of(Media.AUDIO);
+ } else {
+ Log.w(
+ Config.LOGTAG,
+ "actionToMedia can not get media set from unknown action " + action);
+ return Collections.emptySet();
}
}
@@ -313,14 +329,15 @@ public class RtpSessionActivity extends XmppActivity
private void retractSessionProposal() {
final Intent intent = getIntent();
final String action = intent.getAction();
+ final String lastAction = intent.getStringExtra(EXTRA_LAST_ACTION);
final Account account = extractAccount(intent);
final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH));
final String state = intent.getStringExtra(EXTRA_LAST_REPORTED_STATE);
if (!Intent.ACTION_VIEW.equals(action)
|| state == null
|| !END_CARD.contains(RtpEndUserState.valueOf(state))) {
- resetIntent(
- account, with, RtpEndUserState.RETRACTED, actionToMedia(intent.getAction()));
+ final Set media = actionToMedia(lastAction == null ? action : lastAction);
+ resetIntent(account, with, RtpEndUserState.RETRACTED, media);
}
xmppConnectionService
.getJingleConnectionManager()
@@ -388,7 +405,7 @@ public class RtpSessionActivity extends XmppActivity
final List permissions = permissions(getMedia());
if (PermissionUtils.hasPermission(this, permissions, REQUEST_ACCEPT_CALL)) {
putScreenInCallMode();
- checkRecorderAndAcceptCall();
+ acceptCall();
}
}
@@ -405,8 +422,7 @@ public class RtpSessionActivity extends XmppActivity
return permissions.build();
}
- private void checkRecorderAndAcceptCall() {
- checkMicrophoneAvailabilityAsync();
+ private void acceptCall() {
try {
requireRtpConnection().acceptCall();
} catch (final IllegalStateException e) {
@@ -414,58 +430,32 @@ public class RtpSessionActivity extends XmppActivity
}
}
- private void checkMicrophoneAvailabilityAsync() {
- new Thread(new MicrophoneAvailabilityCheck(this)).start();
- }
-
- private static class MicrophoneAvailabilityCheck implements Runnable {
-
- private final WeakReference activityReference;
-
- private MicrophoneAvailabilityCheck(final Activity activity) {
- this.activityReference = new WeakReference<>(activity);
- }
-
- @Override
- public void run() {
- final long start = SystemClock.elapsedRealtime();
- final boolean isMicrophoneAvailable = AppRTCAudioManager.isMicrophoneAvailable();
- final long stop = SystemClock.elapsedRealtime();
- Log.d(Config.LOGTAG, "checking microphone availability took " + (stop - start) + "ms");
- if (isMicrophoneAvailable) {
- return;
- }
- final Activity activity = activityReference.get();
- if (activity == null) {
- return;
- }
- activity.runOnUiThread(
- () ->
- Toast.makeText(
- activity,
- R.string.microphone_unavailable,
- Toast.LENGTH_LONG)
- .show());
- }
- }
-
private void putScreenInCallMode() {
putScreenInCallMode(requireRtpConnection().getMedia());
}
private void putScreenInCallMode(final Set media) {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- if (!media.contains(Media.VIDEO)) {
+ if (Media.audioOnly(media)) {
final JingleRtpConnection rtpConnection =
rtpConnectionReference != null ? rtpConnectionReference.get() : null;
- final AppRTCAudioManager audioManager =
- rtpConnection == null ? null : rtpConnection.getAudioManager();
- if (audioManager == null
- || audioManager.getSelectedAudioDevice()
- == AppRTCAudioManager.AudioDevice.EARPIECE) {
+ final CallIntegration callIntegration =
+ rtpConnection == null ? null : rtpConnection.getCallIntegration();
+ if (callIntegration == null
+ || callIntegration.getSelectedAudioDevice()
+ == CallIntegration.AudioDevice.EARPIECE) {
acquireProximityWakeLock();
}
}
+ lockOrientation(media);
+ }
+
+ private void lockOrientation(final Set media) {
+ if (Media.audioOnly(media)) {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
+ } else {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ }
}
@SuppressLint("WakelockTimeout")
@@ -498,9 +488,8 @@ public class RtpSessionActivity extends XmppActivity
}
}
- private void putProximityWakeLockInProperState(
- final AppRTCAudioManager.AudioDevice audioDevice) {
- if (audioDevice == AppRTCAudioManager.AudioDevice.EARPIECE) {
+ private void putProximityWakeLockInProperState(final CallIntegration.AudioDevice audioDevice) {
+ if (audioDevice == CallIntegration.AudioDevice.EARPIECE) {
acquireProximityWakeLock();
} else {
releaseProximityWakeLock();
@@ -514,6 +503,9 @@ public class RtpSessionActivity extends XmppActivity
public void onNewIntent(final Intent intent) {
Log.d(Config.LOGTAG, this.getClass().getName() + ".onNewIntent()");
super.onNewIntent(intent);
+ if (intent == null) {
+ return;
+ }
setIntent(intent);
if (xmppConnectionService == null) {
Log.d(
@@ -521,32 +513,21 @@ public class RtpSessionActivity extends XmppActivity
"RtpSessionActivity: background service wasn't bound in onNewIntent()");
return;
}
- final Account account = extractAccount(intent);
- final String action = intent.getAction();
- final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH));
- final String sessionId = intent.getStringExtra(EXTRA_SESSION_ID);
- if (sessionId != null) {
- Log.d(Config.LOGTAG, "reinitializing from onNewIntent()");
- if (initializeActivityWithRunningRtpSession(account, with, sessionId)) {
- return;
- }
- if (ACTION_ACCEPT_CALL.equals(intent.getAction())) {
- Log.d(Config.LOGTAG, "accepting call from onNewIntent()");
- requestPermissionsAndAcceptCall();
- resetIntent(intent.getExtras());
- }
- } else if (asList(ACTION_MAKE_VIDEO_CALL, ACTION_MAKE_VOICE_CALL).contains(action)) {
- proposeJingleRtpSession(account, with, actionToMedia(action));
- setWith(account.getRoster().getContact(with), null);
- } else {
- throw new IllegalStateException("received onNewIntent without sessionId");
- }
+ initializeWithIntent(Event.ON_NEW_INTENT, intent);
}
@Override
- void onBackendConnected() {
- final Intent intent = getIntent();
+ protected void onBackendConnected() {
+ final var intent = getIntent();
+ if (intent == null) {
+ return;
+ }
+ initializeWithIntent(Event.ON_BACKEND_CONNECTED, intent);
+ }
+
+ private void initializeWithIntent(final Event event, @NonNull final Intent intent) {
final String action = intent.getAction();
+ Log.d(Config.LOGTAG, "initializeWithIntent(" + event + "," + action + ")");
final Account account = extractAccount(intent);
final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH));
final String sessionId = intent.getStringExtra(EXTRA_SESSION_ID);
@@ -559,10 +540,17 @@ public class RtpSessionActivity extends XmppActivity
requestPermissionsAndAcceptCall();
resetIntent(intent.getExtras());
}
- } else if (asList(ACTION_MAKE_VIDEO_CALL, ACTION_MAKE_VOICE_CALL).contains(action)) {
- proposeJingleRtpSession(account, with, actionToMedia(action));
- setWith(account.getRoster().getContact(with), null);
} else if (Intent.ACTION_VIEW.equals(action)) {
+ final String proposedSessionId = intent.getStringExtra(EXTRA_PROPOSED_SESSION_ID);
+ final JingleConnectionManager.TerminatedRtpSession terminatedRtpSession =
+ xmppConnectionService
+ .getJingleConnectionManager()
+ .getTerminalSessionState(with, proposedSessionId);
+ if (terminatedRtpSession != null) {
+ // termination (due to message error or 'busy' was faster than opening the activity
+ initializeWithTerminatedSessionState(account, with, terminatedRtpSession);
+ return;
+ }
final String extraLastState = intent.getStringExtra(EXTRA_LAST_REPORTED_STATE);
final RtpEndUserState state =
extraLastState == null ? null : RtpEndUserState.valueOf(extraLastState);
@@ -580,10 +568,15 @@ public class RtpSessionActivity extends XmppActivity
.fireJingleRtpConnectionStateUpdates()) {
return;
}
- if (END_CARD.contains(state)
- || xmppConnectionService
+ if (END_CARD.contains(state)) {
+ return;
+ }
+ final String lastAction = intent.getStringExtra(EXTRA_LAST_ACTION);
+ final Set media = actionToMedia(lastAction);
+ if (xmppConnectionService
.getJingleConnectionManager()
.hasMatchingProposal(account, with)) {
+ putScreenInCallMode(media);
return;
}
Log.d(Config.LOGTAG, "restored state (" + state + ") was not an end card. finishing");
@@ -591,7 +584,7 @@ public class RtpSessionActivity extends XmppActivity
}
}
- private void setWith(final RtpEndUserState state) {
+ private void setWidth(final RtpEndUserState state) {
setWith(getWith(), state);
}
@@ -606,24 +599,6 @@ public class RtpSessionActivity extends XmppActivity
}
}
- private void proposeJingleRtpSession(
- final Account account, final Jid with, final Set media) {
- checkMicrophoneAvailabilityAsync();
- if (with.isBareJid()) {
- xmppConnectionService
- .getJingleConnectionManager()
- .proposeJingleRtpSession(account, with, media);
- } else {
- final String sessionId =
- xmppConnectionService
- .getJingleConnectionManager()
- .initializeRtpSession(account, with, media);
- initializeActivityWithRunningRtpSession(account, with, sessionId);
- resetIntent(account, with, sessionId);
- }
- putScreenInCallMode(media);
- }
-
@Override
public void onRequestPermissionsResult(
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
@@ -632,7 +607,7 @@ public class RtpSessionActivity extends XmppActivity
PermissionUtils.removeBluetoothConnect(permissions, grantResults);
if (PermissionUtils.allGranted(permissionResult.grantResults)) {
if (requestCode == REQUEST_ACCEPT_CALL) {
- checkRecorderAndAcceptCall();
+ acceptCall();
} else if (requestCode == REQUEST_ACCEPT_CONTENT) {
acceptContentAdd();
} else if (requestCode == REQUEST_ADD_CONTENT) {
@@ -823,7 +798,7 @@ public class RtpSessionActivity extends XmppActivity
requireRtpConnection().getState())) {
putScreenInCallMode();
}
- setWith(currentState);
+ setWidth(currentState);
updateVideoViews(currentState);
updateStateDisplay(currentState, media, contentAddition);
updateVerifiedShield(verified && STATES_SHOWING_SWITCH_TO_CHAT.contains(currentState));
@@ -871,8 +846,12 @@ public class RtpSessionActivity extends XmppActivity
surfaceViewRenderer.setVisibility(View.VISIBLE);
try {
surfaceViewRenderer.init(requireRtpConnection().getEglBaseContext(), null);
- } catch (final IllegalStateException e) {
- // Log.d(Config.LOGTAG, "SurfaceViewRenderer was already initialized");
+ } catch (final IllegalStateException ignored) {
+ // SurfaceViewRenderer was already initialized
+ } catch (final RuntimeException e) {
+ if (Throwables.getRootCause(e) instanceof GLException glException) {
+ Log.w(Config.LOGTAG, "could not set up hardware renderer", glException);
+ }
}
surfaceViewRenderer.setEnableHardwareScaler(true);
}
@@ -909,6 +888,7 @@ public class RtpSessionActivity extends XmppActivity
case FINDING_DEVICE -> setTitle(R.string.rtp_state_finding_device);
case RINGING -> setTitle(R.string.rtp_state_ringing);
case DECLINED_OR_BUSY -> setTitle(R.string.rtp_state_declined_or_busy);
+ case CONTACT_OFFLINE -> setTitle(R.string.rtp_state_contact_offline);
case CONNECTIVITY_ERROR -> setTitle(R.string.rtp_state_connectivity_error);
case CONNECTIVITY_LOST_ERROR -> setTitle(R.string.rtp_state_connectivity_lost_error);
case RETRACTED -> setTitle(R.string.rtp_state_retracted);
@@ -984,33 +964,34 @@ public class RtpSessionActivity extends XmppActivity
} else if (state == RtpEndUserState.INCOMING_CALL) {
this.binding.rejectCall.setContentDescription(getString(R.string.dismiss_call));
this.binding.rejectCall.setOnClickListener(this::rejectCall);
- this.binding.rejectCall.setImageResource(R.drawable.ic_call_end_white_48dp);
+ this.binding.rejectCall.setImageResource(R.drawable.ic_call_end_24dp);
this.binding.rejectCall.setVisibility(View.VISIBLE);
this.binding.endCall.setVisibility(View.INVISIBLE);
this.binding.acceptCall.setContentDescription(getString(R.string.answer_call));
this.binding.acceptCall.setOnClickListener(this::acceptCall);
- this.binding.acceptCall.setImageResource(R.drawable.ic_call_white_48dp);
+ this.binding.acceptCall.setImageResource(R.drawable.ic_call_24dp);
this.binding.acceptCall.setVisibility(View.VISIBLE);
} else if (state == RtpEndUserState.INCOMING_CONTENT_ADD) {
this.binding.rejectCall.setContentDescription(
getString(R.string.reject_switch_to_video));
this.binding.rejectCall.setOnClickListener(this::rejectContentAdd);
- this.binding.rejectCall.setImageResource(R.drawable.ic_clear_white_48dp);
+ this.binding.rejectCall.setImageResource(R.drawable.ic_clear_24dp);
this.binding.rejectCall.setVisibility(View.VISIBLE);
this.binding.endCall.setVisibility(View.INVISIBLE);
this.binding.acceptCall.setContentDescription(getString(R.string.accept));
this.binding.acceptCall.setOnClickListener((v -> acceptContentAdd(contentAddition)));
- this.binding.acceptCall.setImageResource(R.drawable.ic_baseline_check_24);
+ this.binding.acceptCall.setImageResource(R.drawable.ic_check_24dp);
this.binding.acceptCall.setVisibility(View.VISIBLE);
- } else if (state == RtpEndUserState.DECLINED_OR_BUSY) {
+ } else if (asList(RtpEndUserState.DECLINED_OR_BUSY, RtpEndUserState.CONTACT_OFFLINE)
+ .contains(state)) {
this.binding.rejectCall.setContentDescription(getString(R.string.exit));
this.binding.rejectCall.setOnClickListener(this::exit);
- this.binding.rejectCall.setImageResource(R.drawable.ic_clear_white_48dp);
+ this.binding.rejectCall.setImageResource(R.drawable.ic_clear_24dp);
this.binding.rejectCall.setVisibility(View.VISIBLE);
this.binding.endCall.setVisibility(View.INVISIBLE);
this.binding.acceptCall.setContentDescription(getString(R.string.record_voice_mail));
this.binding.acceptCall.setOnClickListener(this::recordVoiceMail);
- this.binding.acceptCall.setImageResource(R.drawable.ic_voicemail_white_24dp);
+ this.binding.acceptCall.setImageResource(R.drawable.ic_voicemail_24dp);
this.binding.acceptCall.setVisibility(View.VISIBLE);
} else if (asList(
RtpEndUserState.CONNECTIVITY_ERROR,
@@ -1021,18 +1002,18 @@ public class RtpSessionActivity extends XmppActivity
.contains(state)) {
this.binding.rejectCall.setContentDescription(getString(R.string.exit));
this.binding.rejectCall.setOnClickListener(this::exit);
- this.binding.rejectCall.setImageResource(R.drawable.ic_clear_white_48dp);
+ this.binding.rejectCall.setImageResource(R.drawable.ic_clear_24dp);
this.binding.rejectCall.setVisibility(View.VISIBLE);
this.binding.endCall.setVisibility(View.INVISIBLE);
this.binding.acceptCall.setContentDescription(getString(R.string.try_again));
this.binding.acceptCall.setOnClickListener(this::retry);
- this.binding.acceptCall.setImageResource(R.drawable.ic_replay_white_48dp);
+ this.binding.acceptCall.setImageResource(R.drawable.ic_replay_24dp);
this.binding.acceptCall.setVisibility(View.VISIBLE);
} else {
this.binding.rejectCall.setVisibility(View.INVISIBLE);
this.binding.endCall.setContentDescription(getString(R.string.hang_up));
this.binding.endCall.setOnClickListener(this::endCall);
- this.binding.endCall.setImageResource(R.drawable.ic_call_end_white_48dp);
+ this.binding.endCall.setImageResource(R.drawable.ic_call_end_24dp);
this.binding.endCall.setVisibility(View.VISIBLE);
this.binding.acceptCall.setVisibility(View.INVISIBLE);
}
@@ -1056,16 +1037,16 @@ public class RtpSessionActivity extends XmppActivity
private void updateInCallButtonConfiguration(
final RtpEndUserState state, final Set media) {
if (STATES_CONSIDERED_CONNECTED.contains(state) && !isPictureInPicture()) {
- Preconditions.checkArgument(media.size() > 0, "Media must not be empty");
+ Preconditions.checkArgument(!media.isEmpty(), "Media must not be empty");
if (media.contains(Media.VIDEO)) {
final JingleRtpConnection rtpConnection = requireRtpConnection();
updateInCallButtonConfigurationVideo(
rtpConnection.isVideoEnabled(), rtpConnection.isCameraSwitchable());
} else {
- final AppRTCAudioManager audioManager = requireRtpConnection().getAudioManager();
+ final CallIntegration callIntegration = requireRtpConnection().getCallIntegration();
updateInCallButtonConfigurationSpeaker(
- audioManager.getSelectedAudioDevice(),
- audioManager.getAudioDevices().size());
+ callIntegration.getSelectedAudioDevice(),
+ callIntegration.getAudioDevices().size());
this.binding.inCallActionFarRight.setVisibility(View.GONE);
}
if (media.contains(Media.AUDIO)) {
@@ -1074,6 +1055,20 @@ public class RtpSessionActivity extends XmppActivity
} else {
this.binding.inCallActionLeft.setVisibility(View.GONE);
}
+ } else if (STATES_SHOWING_SPEAKER_CONFIGURATION.contains(state)
+ && !isPictureInPicture()
+ && Media.audioOnly(media)) {
+ final CallIntegration callIntegration;
+ try {
+ callIntegration = requireCallIntegration();
+ } catch (final IllegalStateException e) {
+ Log.e(Config.LOGTAG, "can not update InCallButtonConfiguration in state " + state);
+ return;
+ }
+ updateInCallButtonConfigurationSpeaker(
+ callIntegration.getSelectedAudioDevice(),
+ callIntegration.getAudioDevices().size());
+ this.binding.inCallActionFarRight.setVisibility(View.GONE);
} else {
this.binding.inCallActionLeft.setVisibility(View.GONE);
this.binding.inCallActionRight.setVisibility(View.GONE);
@@ -1083,11 +1078,11 @@ public class RtpSessionActivity extends XmppActivity
@SuppressLint("RestrictedApi")
private void updateInCallButtonConfigurationSpeaker(
- final AppRTCAudioManager.AudioDevice selectedAudioDevice, final int numberOfChoices) {
+ final CallIntegration.AudioDevice selectedAudioDevice, final int numberOfChoices) {
switch (selectedAudioDevice) {
case EARPIECE -> {
this.binding.inCallActionRight.setImageResource(
- R.drawable.ic_volume_off_black_24dp);
+ R.drawable.ic_volume_off_24dp);
if (numberOfChoices >= 2) {
this.binding.inCallActionRight.setOnClickListener(this::switchToSpeaker);
} else {
@@ -1096,12 +1091,12 @@ public class RtpSessionActivity extends XmppActivity
}
}
case WIRED_HEADSET -> {
- this.binding.inCallActionRight.setImageResource(R.drawable.ic_headset_black_24dp);
+ this.binding.inCallActionRight.setImageResource(R.drawable.ic_headset_mic_24dp);
this.binding.inCallActionRight.setOnClickListener(null);
this.binding.inCallActionRight.setClickable(false);
}
case SPEAKER_PHONE -> {
- this.binding.inCallActionRight.setImageResource(R.drawable.ic_volume_up_black_24dp);
+ this.binding.inCallActionRight.setImageResource(R.drawable.ic_volume_up_24dp);
if (numberOfChoices >= 2) {
this.binding.inCallActionRight.setOnClickListener(this::switchToEarpiece);
} else {
@@ -1111,7 +1106,7 @@ public class RtpSessionActivity extends XmppActivity
}
case BLUETOOTH -> {
this.binding.inCallActionRight.setImageResource(
- R.drawable.ic_bluetooth_audio_black_24dp);
+ R.drawable.ic_bluetooth_audio_24dp);
this.binding.inCallActionRight.setOnClickListener(null);
this.binding.inCallActionRight.setClickable(false);
}
@@ -1125,17 +1120,17 @@ public class RtpSessionActivity extends XmppActivity
this.binding.inCallActionRight.setVisibility(View.VISIBLE);
if (isCameraSwitchable) {
this.binding.inCallActionFarRight.setImageResource(
- R.drawable.ic_flip_camera_android_black_24dp);
+ R.drawable.ic_flip_camera_android_24dp);
this.binding.inCallActionFarRight.setVisibility(View.VISIBLE);
this.binding.inCallActionFarRight.setOnClickListener(this::switchCamera);
} else {
this.binding.inCallActionFarRight.setVisibility(View.GONE);
}
if (videoEnabled) {
- this.binding.inCallActionRight.setImageResource(R.drawable.ic_videocam_black_24dp);
+ this.binding.inCallActionRight.setImageResource(R.drawable.ic_videocam_24dp);
this.binding.inCallActionRight.setOnClickListener(this::disableVideo);
} else {
- this.binding.inCallActionRight.setImageResource(R.drawable.ic_videocam_off_black_24dp);
+ this.binding.inCallActionRight.setImageResource(R.drawable.ic_videocam_off_24dp);
this.binding.inCallActionRight.setOnClickListener(this::enableVideo);
}
}
@@ -1189,10 +1184,10 @@ public class RtpSessionActivity extends XmppActivity
@SuppressLint("RestrictedApi")
private void updateInCallButtonConfigurationMicrophone(final boolean microphoneEnabled) {
if (microphoneEnabled) {
- this.binding.inCallActionLeft.setImageResource(R.drawable.ic_mic_black_24dp);
+ this.binding.inCallActionLeft.setImageResource(R.drawable.ic_mic_24dp);
this.binding.inCallActionLeft.setOnClickListener(this::disableMicrophone);
} else {
- this.binding.inCallActionLeft.setImageResource(R.drawable.ic_mic_off_black_24dp);
+ this.binding.inCallActionLeft.setImageResource(R.drawable.ic_mic_off_24dp);
this.binding.inCallActionLeft.setOnClickListener(this::enableMicrophone);
}
this.binding.inCallActionLeft.setVisibility(View.VISIBLE);
@@ -1322,21 +1317,17 @@ public class RtpSessionActivity extends XmppActivity
}
}
- private void switchToEarpiece(View view) {
- requireRtpConnection()
- .getAudioManager()
- .setDefaultAudioDevice(AppRTCAudioManager.AudioDevice.EARPIECE);
+ private void switchToEarpiece(final View view) {
+ requireCallIntegration().setAudioDevice(CallIntegration.AudioDevice.EARPIECE);
acquireProximityWakeLock();
}
- private void switchToSpeaker(View view) {
- requireRtpConnection()
- .getAudioManager()
- .setDefaultAudioDevice(AppRTCAudioManager.AudioDevice.SPEAKER_PHONE);
+ private void switchToSpeaker(final View view) {
+ requireCallIntegration().setAudioDevice(CallIntegration.AudioDevice.SPEAKER_PHONE);
releaseProximityWakeLock();
}
- private void retry(View view) {
+ private void retry(final View view) {
final Intent intent = getIntent();
final Account account = extractAccount(intent);
final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH));
@@ -1345,7 +1336,7 @@ public class RtpSessionActivity extends XmppActivity
final Set media = actionToMedia(lastAction == null ? action : lastAction);
this.rtpConnectionReference = null;
Log.d(Config.LOGTAG, "attempting retry with " + with.toEscapedString());
- proposeJingleRtpSession(account, with, media);
+ CallIntegrationConnectionService.placeCall(xmppConnectionService, account, with, media);
}
private void exit(final View view) {
@@ -1384,6 +1375,33 @@ public class RtpSessionActivity extends XmppActivity
return connection;
}
+ private CallIntegration requireCallIntegration() {
+ return requireOngoingRtpSession().getCallIntegration();
+ }
+
+ private OngoingRtpSession requireOngoingRtpSession() {
+ final JingleRtpConnection connection =
+ this.rtpConnectionReference != null ? this.rtpConnectionReference.get() : null;
+ if (connection != null) {
+ return connection;
+ }
+ final Intent currentIntent = getIntent();
+ final String withExtra =
+ currentIntent == null ? null : currentIntent.getStringExtra(EXTRA_WITH);
+ final var account = extractAccount(currentIntent);
+ if (withExtra == null) {
+ throw new IllegalStateException("Current intent has no EXTRA_WITH");
+ }
+ final var matching =
+ xmppConnectionService
+ .getJingleConnectionManager()
+ .matchingProposal(account, Jid.of(withExtra));
+ if (matching.isPresent()) {
+ return matching.get();
+ }
+ throw new IllegalStateException("No matching session proposal");
+ }
+
@Override
public void onJingleRtpConnectionUpdate(
Account account, Jid with, final String sessionId, RtpEndUserState state) {
@@ -1410,6 +1428,7 @@ public class RtpSessionActivity extends XmppActivity
final AbstractJingleConnection.Id id = requireRtpConnection().getId();
final boolean verified = requireRtpConnection().isVerified();
final Set media = getMedia();
+ lockOrientation(media);
final ContentAddition contentAddition = getPendingContentAddition();
final Contact contact = getWith();
if (account == id.account && id.with.equals(with) && id.sessionId.equals(sessionId)) {
@@ -1440,8 +1459,8 @@ public class RtpSessionActivity extends XmppActivity
@Override
public void onAudioDeviceChanged(
- final AppRTCAudioManager.AudioDevice selectedAudioDevice,
- final Set availableAudioDevices) {
+ final CallIntegration.AudioDevice selectedAudioDevice,
+ final Set availableAudioDevices) {
Log.d(
Config.LOGTAG,
"onAudioDeviceChanged in activity: selected:"
@@ -1449,19 +1468,26 @@ public class RtpSessionActivity extends XmppActivity
+ ", available:"
+ availableAudioDevices);
try {
- final RtpEndUserState endUserState = requireRtpConnection().getEndUserState();
- final Set media = getMedia();
+ final OngoingRtpSession ongoingRtpSession = requireOngoingRtpSession();
+ final RtpEndUserState endUserState;
+ if (ongoingRtpSession instanceof JingleRtpConnection jingleRtpConnection) {
+ endUserState = jingleRtpConnection.getEndUserState();
+ } else {
+ // for session proposals all end user states are functionally the same
+ endUserState = RtpEndUserState.RINGING;
+ }
+ final Set media = ongoingRtpSession.getMedia();
if (END_CARD.contains(endUserState)) {
Log.d(
Config.LOGTAG,
"onAudioDeviceChanged() nothing to do because end card has been reached");
} else {
- if (Media.audioOnly(media) && endUserState == RtpEndUserState.CONNECTED) {
- final AppRTCAudioManager audioManager =
- requireRtpConnection().getAudioManager();
+ if (Media.audioOnly(media)
+ && STATES_SHOWING_SPEAKER_CONFIGURATION.contains(endUserState)) {
+ final CallIntegration callIntegration = requireCallIntegration();
updateInCallButtonConfigurationSpeaker(
- audioManager.getSelectedAudioDevice(),
- audioManager.getAudioDevices().size());
+ callIntegration.getSelectedAudioDevice(),
+ callIntegration.getAudioDevices().size());
}
Log.d(
Config.LOGTAG,
@@ -1487,16 +1513,17 @@ public class RtpSessionActivity extends XmppActivity
if (withExtra == null) {
return;
}
+ final Set media = actionToMedia(currentIntent.getStringExtra(EXTRA_LAST_ACTION));
if (Jid.ofEscaped(withExtra).asBareJid().equals(with)) {
runOnUiThread(
() -> {
updateVerifiedShield(false);
updateStateDisplay(state);
- updateButtonConfiguration(state);
+ updateButtonConfiguration(state, media, null);
updateIncomingCallScreen(state);
invalidateOptionsMenu();
});
- resetIntent(account, with, state, actionToMedia(currentIntent.getAction()));
+ resetIntent(account, with, state, media);
}
}
@@ -1525,4 +1552,9 @@ public class RtpSessionActivity extends XmppActivity
private static boolean emptyReference(final WeakReference> weakReference) {
return weakReference == null || weakReference.get() == null;
}
+
+ private enum Event {
+ ON_BACKEND_CONNECTED,
+ ON_NEW_INTENT
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ScanActivity.java b/src/main/java/eu/siacs/conversations/ui/ScanActivity.java
index dca4daacf..983f21dc8 100644
--- a/src/main/java/eu/siacs/conversations/ui/ScanActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ScanActivity.java
@@ -190,7 +190,6 @@ public final class ScanActivity extends AppCompatActivity implements SurfaceText
@Override
protected void onResume() {
super.onResume();
- SettingsUtils.applyScreenshotPreventionSetting(this);
maybeOpenCamera();
}
diff --git a/src/main/java/eu/siacs/conversations/ui/SearchActivity.java b/src/main/java/eu/siacs/conversations/ui/SearchActivity.java
index 7967ba284..e78ff1534 100644
--- a/src/main/java/eu/siacs/conversations/ui/SearchActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/SearchActivity.java
@@ -44,8 +44,10 @@ import android.widget.AdapterView;
import android.widget.EditText;
import android.widget.Toast;
+import androidx.core.content.ContextCompat;
import androidx.databinding.DataBindingUtil;
+import com.google.android.material.color.MaterialColors;
import com.google.common.base.Strings;
import java.lang.ref.WeakReference;
@@ -67,7 +69,6 @@ import eu.siacs.conversations.ui.util.DateSeparator;
import eu.siacs.conversations.ui.util.ListViewUtils;
import eu.siacs.conversations.ui.util.PendingItem;
import eu.siacs.conversations.ui.util.ShareUtil;
-import eu.siacs.conversations.ui.util.StyledAttributes;
import eu.siacs.conversations.utils.FtsUtils;
import eu.siacs.conversations.utils.MessageUtils;
import eu.siacs.conversations.utils.UIHelper;
@@ -99,6 +100,7 @@ public class SearchActivity extends XmppActivity implements TextWatcher, OnSearc
}
super.onCreate(bundle);
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_search);
+ Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
setSupportActionBar(this.binding.toolbar);
configureActionBar(getSupportActionBar());
this.messageListAdapter = new MessageAdapter(this, this.messages, uuid == null);
@@ -257,7 +259,7 @@ public class SearchActivity extends XmppActivity implements TextWatcher, OnSearc
}
@Override
- void onBackendConnected() {
+ protected void onBackendConnected() {
final List searchTerm = pendingSearch.pop();
if (searchTerm != null && currentSearch.watch(searchTerm)) {
xmppConnectionService.search(searchTerm, uuid,this);
@@ -267,12 +269,12 @@ public class SearchActivity extends XmppActivity implements TextWatcher, OnSearc
private void changeBackground(boolean hasSearch, boolean hasResults) {
if (hasSearch) {
if (hasResults) {
- binding.searchResults.setBackgroundColor(StyledAttributes.getColor(this, R.attr.color_background_secondary));
+ binding.searchResults.setBackgroundColor(MaterialColors.getColor(binding.searchResults, com.google.android.material.R.attr.colorSurface));
} else {
- binding.searchResults.setBackground(StyledAttributes.getDrawable(this, R.attr.activity_background_no_results));
+ binding.searchResults.setBackgroundResource(R.drawable.background_no_results);
}
} else {
- binding.searchResults.setBackground(StyledAttributes.getDrawable(this, R.attr.activity_background_search));
+ binding.searchResults.setBackgroundResource(R.drawable.background_search);
}
}
@@ -292,14 +294,14 @@ public class SearchActivity extends XmppActivity implements TextWatcher, OnSearc
if (!currentSearch.watch(term)) {
return;
}
- if (term.size() > 0) {
- xmppConnectionService.search(term, uuid,this);
- } else {
+ if (term.isEmpty()) {
MessageSearchTask.cancelRunningTasks();
this.messages.clear();
messageListAdapter.setHighlightedTerm(null);
messageListAdapter.notifyDataSetChanged();
changeBackground(false, false);
+ } else {
+ xmppConnectionService.search(term, uuid,this);
}
}
@@ -311,7 +313,7 @@ public class SearchActivity extends XmppActivity implements TextWatcher, OnSearc
DateSeparator.addAll(messages);
this.messages.addAll(messages);
messageListAdapter.notifyDataSetChanged();
- changeBackground(true, messages.size() > 0);
+ changeBackground(true, !messages.isEmpty());
ListViewUtils.scrollToBottom(this.binding.searchResults);
});
}
diff --git a/src/main/java/eu/siacs/conversations/ui/SettingsFragment.java b/src/main/java/eu/siacs/conversations/ui/SettingsFragment.java
deleted file mode 100644
index 9378d82b7..000000000
--- a/src/main/java/eu/siacs/conversations/ui/SettingsFragment.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package eu.siacs.conversations.ui;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.preference.Preference;
-import android.preference.PreferenceCategory;
-import android.preference.PreferenceFragment;
-import android.preference.PreferenceScreen;
-import android.text.TextUtils;
-import android.widget.ListView;
-
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.utils.Compatibility;
-
-public class SettingsFragment extends PreferenceFragment {
-
- private String page = null;
-
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- addPreferencesFromResource(R.xml.preferences);
-
- // Remove from standard preferences if the flag ONLY_INTERNAL_STORAGE is false
- if (!Config.ONLY_INTERNAL_STORAGE) {
- PreferenceCategory mCategory = (PreferenceCategory) findPreference("security_options");
- if (mCategory != null) {
- Preference cleanCache = findPreference("clean_cache");
- Preference cleanPrivateStorage = findPreference("clean_private_storage");
- mCategory.removePreference(cleanCache);
- mCategory.removePreference(cleanPrivateStorage);
- }
- }
- Compatibility.removeUnusedPreferences(this);
-
- if (!TextUtils.isEmpty(page)) {
- openPreferenceScreen(page);
- }
-
- }
-
- @Override
- public void onActivityCreated(Bundle bundle) {
- super.onActivityCreated(bundle);
-
- final ListView listView = getActivity().findViewById(android.R.id.list);
- if (listView != null) {
- listView.setDivider(null);
- }
- }
-
- public void setActivityIntent(final Intent intent) {
- boolean wasEmpty = TextUtils.isEmpty(page);
- if (intent != null) {
- if (Intent.ACTION_VIEW.equals(intent.getAction())) {
- if (intent.getExtras() != null) {
- this.page = intent.getExtras().getString("page");
- if (wasEmpty) {
- openPreferenceScreen(page);
- }
- }
- }
- }
- }
-
- private void openPreferenceScreen(final String screenName) {
- final Preference pref = findPreference(screenName);
- if (pref instanceof PreferenceScreen) {
- final PreferenceScreen preferenceScreen = (PreferenceScreen) pref;
- getActivity().setTitle(preferenceScreen.getTitle());
- preferenceScreen.setDependency("");
- setPreferenceScreen((PreferenceScreen) pref);
- }
- }
-}
diff --git a/src/main/java/eu/siacs/conversations/ui/ShareLocationActivity.java b/src/main/java/eu/siacs/conversations/ui/ShareLocationActivity.java
index 7e53fe897..c7ddcc01c 100644
--- a/src/main/java/eu/siacs/conversations/ui/ShareLocationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ShareLocationActivity.java
@@ -5,7 +5,6 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Location;
import android.location.LocationListener;
-import android.os.Build;
import android.os.Bundle;
import android.view.View;
@@ -15,11 +14,6 @@ import androidx.databinding.DataBindingUtil;
import com.google.android.material.snackbar.Snackbar;
import com.google.common.math.DoubleMath;
-import org.osmdroid.api.IGeoPoint;
-import org.osmdroid.util.GeoPoint;
-
-import java.math.RoundingMode;
-
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.ActivityShareLocationBinding;
@@ -27,7 +21,11 @@ import eu.siacs.conversations.ui.util.LocationHelper;
import eu.siacs.conversations.ui.widget.Marker;
import eu.siacs.conversations.ui.widget.MyLocation;
import eu.siacs.conversations.utils.LocationProvider;
-import eu.siacs.conversations.utils.ThemeHelper;
+
+import org.osmdroid.api.IGeoPoint;
+import org.osmdroid.util.GeoPoint;
+
+import java.math.RoundingMode;
public class ShareLocationActivity extends LocationActivity implements LocationListener {
@@ -58,6 +56,7 @@ public class ShareLocationActivity extends LocationActivity implements LocationL
super.onCreate(savedInstanceState);
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_share_location);
+ Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
setSupportActionBar(binding.toolbar);
configureActionBar(getSupportActionBar());
setupMapView(binding.map, LocationProvider.getGeoPoint(this));
@@ -71,13 +70,12 @@ public class ShareLocationActivity extends LocationActivity implements LocationL
this.snackBar.setAction(R.string.enable, view -> {
if (isLocationEnabledAndAllowed()) {
updateUi();
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !hasLocationPermissions()) {
+ } else if (!hasLocationPermissions()) {
requestPermissions(REQUEST_CODE_SNACKBAR_PRESSED);
} else if (!isLocationEnabled()) {
startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS));
}
});
- ThemeHelper.fix(this.snackBar);
this.binding.shareButton.setOnClickListener(this::shareLocation);
@@ -87,7 +85,7 @@ public class ShareLocationActivity extends LocationActivity implements LocationL
if (!marker_fixed_to_loc) {
if (!isLocationEnabled()) {
startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS));
- } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ } else {
requestPermissions(REQUEST_CODE_FAB_PRESSED);
}
}
@@ -117,16 +115,9 @@ public class ShareLocationActivity extends LocationActivity implements LocationL
@NonNull final int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- if (grantResults.length > 0 &&
- grantResults[0] != PackageManager.PERMISSION_GRANTED &&
- Build.VERSION.SDK_INT >= 23 &&
- permissions.length > 0 &&
- (
- Manifest.permission.LOCATION_HARDWARE.equals(permissions[0]) ||
- Manifest.permission.ACCESS_FINE_LOCATION.equals(permissions[0]) ||
- Manifest.permission.ACCESS_COARSE_LOCATION.equals(permissions[0])
- ) &&
- !shouldShowRequestPermissionRationale(permissions[0])) {
+ if (grantResults.length > 0 && grantResults[0] != PackageManager.PERMISSION_GRANTED && permissions.length > 0 && (
+ Manifest.permission.LOCATION_HARDWARE.equals(permissions[0]) || Manifest.permission.ACCESS_FINE_LOCATION.equals(permissions[0]) || Manifest.permission.ACCESS_COARSE_LOCATION.equals(permissions[0])
+ ) && !shouldShowRequestPermissionRationale(permissions[0])) {
noAskAgain = true;
}
@@ -172,7 +163,7 @@ public class ShareLocationActivity extends LocationActivity implements LocationL
}
@Override
- public void onLocationChanged(final Location location) {
+ public void onLocationChanged(@NonNull final Location location) {
if (this.myLoc == null) {
this.marker_fixed_to_loc = true;
}
@@ -206,7 +197,7 @@ public class ShareLocationActivity extends LocationActivity implements LocationL
}
private boolean isLocationEnabledAndAllowed() {
- return this.hasLocationFeature && (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || this.hasLocationPermissions()) && this.isLocationEnabled();
+ return this.hasLocationFeature && this.hasLocationPermissions() && this.isLocationEnabled();
}
private void toggleFixedLocation() {
@@ -229,8 +220,8 @@ public class ShareLocationActivity extends LocationActivity implements LocationL
if (isLocationEnabledAndAllowed()) {
this.binding.fab.setVisibility(View.VISIBLE);
runOnUiThread(() -> {
- this.binding.fab.setImageResource(marker_fixed_to_loc ? R.drawable.ic_gps_fixed_white_24dp :
- R.drawable.ic_gps_not_fixed_white_24dp);
+ this.binding.fab.setImageResource(marker_fixed_to_loc ? R.drawable.ic_gps_fixed_24dp :
+ R.drawable.ic_gps_not_fixed_24dp);
this.binding.fab.setContentDescription(getResources().getString(
marker_fixed_to_loc ? R.string.action_unfix_from_location : R.string.action_fix_to_location
));
diff --git a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java
index 5dfc4e102..fd7365669 100644
--- a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java
@@ -9,6 +9,8 @@ import android.view.Menu;
import android.view.MenuItem;
import android.widget.Toast;
+import androidx.annotation.NonNull;
+import androidx.databinding.DataBindingUtil;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
@@ -20,6 +22,7 @@ import java.util.stream.Collectors;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
+import eu.siacs.conversations.databinding.ActivityShareWithBinding;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Bookmark;
import eu.siacs.conversations.entities.Contact;
@@ -29,7 +32,11 @@ import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.ui.adapter.ConversationAdapter;
import eu.siacs.conversations.xmpp.Jid;
-public class ShareWithActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate {
+import java.util.ArrayList;
+import java.util.List;
+
+public class ShareWithActivity extends XmppActivity
+ implements XmppConnectionService.OnConversationUpdate {
private static final int REQUEST_STORAGE_PERMISSION = 0x733f32;
private Conversation mPendingConversation = null;
@@ -56,11 +63,10 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
private final List tags = new ArrayList<>();
-
- protected void onActivityResult(int requestCode, int resultCode, final Intent data) {
+ protected void onActivityResult(
+ final int requestCode, final int resultCode, final Intent data) {
super.onActivityResult(requestCode, resultCode, data);
- if (requestCode == REQUEST_START_NEW_CONVERSATION
- && resultCode == RESULT_OK) {
+ if (requestCode == REQUEST_START_NEW_CONVERSATION && resultCode == RESULT_OK) {
share.contact = data.getStringExtra("contact");
share.account = data.getStringExtra(EXTRA_ACCOUNT);
}
@@ -73,7 +79,10 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
}
@Override
- public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
+ public void onRequestPermissionsResult(
+ final int requestCode,
+ @NonNull final String[] permissions,
+ @NonNull final int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (grantResults.length > 0)
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
@@ -85,27 +94,34 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
}
}
} else {
- Toast.makeText(this, getString(R.string.no_storage_permission, getString(R.string.app_name)), Toast.LENGTH_SHORT).show();
+ Toast.makeText(
+ this,
+ getString(
+ R.string.no_storage_permission,
+ getString(R.string.app_name)),
+ Toast.LENGTH_SHORT)
+ .show();
}
}
@Override
- protected void onCreate(Bundle savedInstanceState) {
+ protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_share_with);
+ final ActivityShareWithBinding binding =
+ DataBindingUtil.setContentView(this, R.layout.activity_share_with);
setSupportActionBar(findViewById(R.id.toolbar));
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
getSupportActionBar().setHomeButtonEnabled(false);
}
- setTitle(getString(R.string.title_activity_sharewith));
+ setTitle(getString(R.string.title_activity_share_with));
- RecyclerView mListView = findViewById(R.id.choose_conversation_list);
mAdapter = new ConversationAdapter(this, this.mConversations, this.tags);
- mListView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
- mListView.setAdapter(mAdapter);
+ binding.chooseConversationList.setLayoutManager(
+ new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
+ binding.chooseConversationList.setAdapter(mAdapter);
mAdapter.setConversationClickListener((view, conversation) -> share(conversation));
this.share = new Share();
}
@@ -120,8 +136,9 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.action_add:
- final Intent intent = new Intent(getApplicationContext(), ChooseContactActivity.class);
- intent.putExtra("direct_search",true);
+ final Intent intent =
+ new Intent(getApplicationContext(), ChooseContactActivity.class);
+ intent.putExtra("direct_search", true);
startActivityForResult(intent, REQUEST_START_NEW_CONVERSATION);
return true;
}
@@ -141,7 +158,8 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
if (Intent.ACTION_SEND.equals(action)) {
final String text = intent.getStringExtra(Intent.EXTRA_TEXT);
final Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM);
- final boolean asQuote = intent.getBooleanExtra(ConversationsActivity.EXTRA_AS_QUOTE, false);
+ final boolean asQuote =
+ intent.getBooleanExtra(ConversationsActivity.EXTRA_AS_QUOTE, false);
if (data != null && "geo".equals(data.getScheme())) {
this.share.uris.clear();
@@ -159,14 +177,16 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
this.share.uris = uris == null ? new ArrayList<>() : uris;
}
if (xmppConnectionServiceBound) {
- xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.uris.size() == 0, false);
+ xmppConnectionService.populateWithOrderedConversations(
+ mConversations, this.share.uris.isEmpty(), false);
}
-
}
@Override
- void onBackendConnected() {
- if (xmppConnectionServiceBound && share != null && ((share.contact != null && share.account != null))) {
+ protected void onBackendConnected() {
+ if (xmppConnectionServiceBound
+ && share != null
+ && ((share.contact != null && share.account != null))) {
share();
return;
}
@@ -175,32 +195,35 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
private void share() {
final Conversation conversation;
- Account account;
- try {
- account = xmppConnectionService.findAccountByJid(Jid.ofEscaped(share.account));
- } catch (final IllegalArgumentException e) {
- account = null;
- }
- if (account == null) {
- return;
- }
+ Account account;
+ try {
+ account = xmppConnectionService.findAccountByJid(Jid.ofEscaped(share.account));
+ } catch (final IllegalArgumentException e) {
+ account = null;
+ }
+ if (account == null) {
+ return;
+ }
- try {
- conversation = xmppConnectionService.findOrCreateConversation(account, Jid.of(share.contact), null, false, false, true, null);
- } catch (final IllegalArgumentException e) {
- return;
- }
+ try {
+ conversation =
+ xmppConnectionService.findOrCreateConversation(
+ account, Jid.of(share.contact), null, false, false, true, null);
+
+ } catch (final IllegalArgumentException e) {
+ return;
+ }
share(conversation);
}
private void share(final Conversation conversation) {
- if (share.uris.size() != 0 && !hasStoragePermission(REQUEST_STORAGE_PERMISSION)) {
+ if (!share.uris.isEmpty() && !hasStoragePermission(REQUEST_STORAGE_PERMISSION)) {
mPendingConversation = conversation;
return;
}
- Intent intent = new Intent(this, ConversationsActivity.class);
+ final Intent intent = new Intent(this, ConversationsActivity.class);
intent.putExtra(ConversationsActivity.EXTRA_CONVERSATION, conversation.getUuid());
- if (share.uris.size() > 0) {
+ if (!share.uris.isEmpty()) {
intent.setAction(Intent.ACTION_SEND_MULTIPLE);
intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, share.uris);
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
@@ -215,15 +238,20 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer
try {
startActivity(intent);
} catch (SecurityException e) {
- Toast.makeText(this, R.string.sharing_application_not_grant_permission, Toast.LENGTH_SHORT).show();
+ Toast.makeText(
+ this,
+ R.string.sharing_application_not_grant_permission,
+ Toast.LENGTH_SHORT)
+ .show();
return;
}
finish();
}
public void refreshUiReal() {
- //TODO inject desired order to not resort on refresh
- xmppConnectionService.populateWithOrderedConversations(mConversations, this.share != null && this.share.uris.size() == 0, false);
+ // TODO inject desired order to not resort on refresh
+ xmppConnectionService.populateWithOrderedConversations(
+ mConversations, this.share != null && this.share.uris.isEmpty(), false);
refreshTags();
mAdapter.notifyDataSetChanged();
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ShortcutActivity.java b/src/main/java/eu/siacs/conversations/ui/ShortcutActivity.java
index ff935beae..66b3fb886 100644
--- a/src/main/java/eu/siacs/conversations/ui/ShortcutActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ShortcutActivity.java
@@ -45,7 +45,7 @@ public class ShortcutActivity extends AbstractSearchableListItemActivity {
}
@Override
- protected void onStart() {
+ public void onStart() {
super.onStart();
ActionBar bar = getSupportActionBar();
if(bar != null){
diff --git a/src/main/java/eu/siacs/conversations/ui/ShowLocationActivity.java b/src/main/java/eu/siacs/conversations/ui/ShowLocationActivity.java
index d4b6a2e30..8ed7e1da6 100644
--- a/src/main/java/eu/siacs/conversations/ui/ShowLocationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ShowLocationActivity.java
@@ -9,6 +9,7 @@ import android.location.Location;
import android.location.LocationListener;
import android.net.Uri;
import android.os.Bundle;
+import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -17,12 +18,8 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil;
-import org.jetbrains.annotations.NotNull;
-import org.osmdroid.util.GeoPoint;
-
-import java.util.HashMap;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+import com.google.common.base.Strings;
+import com.google.common.primitives.Doubles;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
@@ -31,8 +28,13 @@ import eu.siacs.conversations.ui.util.LocationHelper;
import eu.siacs.conversations.ui.util.UriHelper;
import eu.siacs.conversations.ui.widget.Marker;
import eu.siacs.conversations.ui.widget.MyLocation;
+import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.utils.LocationProvider;
+import org.osmdroid.util.GeoPoint;
+
+import java.util.Map;
+
public class ShowLocationActivity extends LocationActivity implements LocationListener {
private GeoPoint loc = LocationProvider.FALLBACK;
@@ -49,79 +51,49 @@ public class ShowLocationActivity extends LocationActivity implements LocationLi
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_show_location);
setSupportActionBar(binding.toolbar);
+ Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
+
configureActionBar(getSupportActionBar());
setupMapView(this.binding.map, this.loc);
this.binding.fab.setOnClickListener(view -> startNavigation());
final Intent intent = getIntent();
- if (intent != null) {
- final String action = intent.getAction();
- if (action == null) {
- return;
- }
- switch (action) {
- case "eu.siacs.conversations.location.show":
- if (intent.hasExtra("longitude") && intent.hasExtra("latitude")) {
- final double longitude = intent.getDoubleExtra("longitude", 0);
- final double latitude = intent.getDoubleExtra("latitude", 0);
- this.loc = new GeoPoint(latitude, longitude);
- }
- break;
- case Intent.ACTION_VIEW:
- final Uri geoUri = intent.getData();
-
- // Attempt to set zoom level if the geo URI specifies it
- if (geoUri != null) {
- final HashMap query =
- UriHelper.parseQueryString(geoUri.getQuery());
-
- // Check for zoom level.
- final String z = query.get("z");
- if (z != null) {
- try {
- mapController.setZoom(Double.valueOf(z));
- } catch (final Exception ignored) {
- }
- }
-
- // Check for the actual geo query.
- boolean posInQuery = false;
- final String q = query.get("q");
- if (q != null) {
- final Pattern latlng =
- Pattern.compile(
- "/^([-+]?[0-9]+(\\.[0-9]+)?),([-+]?[0-9]+(\\.[0-9]+)?)(\\(.*\\))?/");
- final Matcher m = latlng.matcher(q);
- if (m.matches()) {
- try {
- this.loc =
- new GeoPoint(
- Double.valueOf(m.group(1)),
- Double.valueOf(m.group(3)));
- posInQuery = true;
- } catch (final Exception ignored) {
- }
- }
- }
-
- final String schemeSpecificPart = geoUri.getSchemeSpecificPart();
- if (schemeSpecificPart != null && !schemeSpecificPart.isEmpty()) {
- try {
- final GeoPoint latlong =
- LocationHelper.parseLatLong(schemeSpecificPart);
- if (latlong != null && !posInQuery) {
- this.loc = latlong;
- }
- } catch (final NumberFormatException ignored) {
- }
- }
- }
-
- break;
- }
- updateLocationMarkers();
+ if (intent == null) {
+ return;
}
+ final String action = intent.getAction();
+ switch (Strings.nullToEmpty(action)) {
+ case "eu.siacs.conversations.location.show":
+ if (intent.hasExtra("longitude") && intent.hasExtra("latitude")) {
+ final double longitude = intent.getDoubleExtra("longitude", 0);
+ final double latitude = intent.getDoubleExtra("latitude", 0);
+ this.loc = new GeoPoint(latitude, longitude);
+ }
+ break;
+ case Intent.ACTION_VIEW:
+ final Uri uri = intent.getData();
+ if (uri == null) {
+ break;
+ }
+ final GeoPoint point;
+ try {
+ point = GeoHelper.parseGeoPoint(uri);
+ } catch (final Exception e) {
+ break;
+ }
+ this.loc = point;
+ final Map query = UriHelper.parseQueryString(uri.getQuery());
+ final String z = query.get("z");
+ final Double zoom = Strings.isNullOrEmpty(z) ? null : Doubles.tryParse(z);
+ if (zoom != null) {
+ Log.d(Config.LOGTAG, "inferring zoom level " + zoom + " from geo uri");
+ mapController.setZoom(zoom);
+ gotoLoc(false);
+ }
+ break;
+ }
+ updateLocationMarkers();
}
@Override
@@ -149,7 +121,7 @@ public class ShowLocationActivity extends LocationActivity implements LocationLi
}
@Override
- public boolean onCreateOptionsMenu(@NotNull final Menu menu) {
+ public boolean onCreateOptionsMenu(@NonNull final Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.menu_show_location, menu);
updateUi();
@@ -172,37 +144,43 @@ public class ShowLocationActivity extends LocationActivity implements LocationLi
@Override
public boolean onOptionsItemSelected(final MenuItem item) {
- switch (item.getItemId()) {
- case R.id.action_copy_location:
- final ClipboardManager clipboard =
- (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
- if (clipboard != null) {
- final ClipData clip =
- ClipData.newPlainText("location", createGeoUri().toString());
- clipboard.setPrimaryClip(clip);
- Toast.makeText(this, R.string.url_copied_to_clipboard, Toast.LENGTH_SHORT)
- .show();
- }
- return true;
- case R.id.action_share_location:
- final Intent shareIntent = new Intent();
- shareIntent.setAction(Intent.ACTION_SEND);
- shareIntent.putExtra(Intent.EXTRA_TEXT, createGeoUri().toString());
- shareIntent.setType("text/plain");
- try {
- startActivity(Intent.createChooser(shareIntent, getText(R.string.share_with)));
- } catch (final ActivityNotFoundException e) {
- // This should happen only on faulty androids because normally chooser is always
- // available
- Toast.makeText(
- this,
- R.string.no_application_found_to_open_file,
- Toast.LENGTH_SHORT)
- .show();
- }
- return true;
+ final var itemId = item.getItemId();
+ if (itemId == R.id.action_copy_location) {
+ final ClipboardManager clipboard = getSystemService(ClipboardManager.class);
+ final ClipData clip = ClipData.newPlainText("location", createGeoUri().toString());
+ clipboard.setPrimaryClip(clip);
+ Toast.makeText(this, R.string.url_copied_to_clipboard, Toast.LENGTH_SHORT).show();
+ return true;
+ } else if (itemId == R.id.action_share_location) {
+ final Intent shareIntent = new Intent();
+ shareIntent.setAction(Intent.ACTION_SEND);
+ shareIntent.putExtra(Intent.EXTRA_TEXT, createGeoUri().toString());
+ shareIntent.setType("text/plain");
+ try {
+ startActivity(Intent.createChooser(shareIntent, getText(R.string.share_with)));
+ } catch (final ActivityNotFoundException e) {
+ // This should happen only on faulty androids because normally chooser is always
+ // available
+ Toast.makeText(this, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT)
+ .show();
+ }
+ return true;
+ } else if (itemId == R.id.action_open_with) {
+ final Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setData(createGeoUri());
+ try {
+ startActivity(Intent.createChooser(intent, getText(R.string.open_with)));
+ } catch (final ActivityNotFoundException e) {
+ // This should happen only on faulty androids because normally chooser is always
+ // available
+ Toast.makeText(this, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT)
+ .show();
+ }
+ return true;
+
+ } else {
+ return super.onOptionsItemSelected(item);
}
- return super.onOptionsItemSelected(item);
}
private void startNavigation() {
@@ -228,7 +206,7 @@ public class ShowLocationActivity extends LocationActivity implements LocationLi
}
@Override
- public void onLocationChanged(@NotNull final Location location) {
+ public void onLocationChanged(@NonNull final Location location) {
if (LocationHelper.isBetterLocation(location, this.myLoc)) {
this.myLoc = location;
updateLocationMarkers();
@@ -239,8 +217,8 @@ public class ShowLocationActivity extends LocationActivity implements LocationLi
public void onStatusChanged(final String provider, final int status, final Bundle extras) {}
@Override
- public void onProviderEnabled(@NotNull final String provider) {}
+ public void onProviderEnabled(@NonNull final String provider) {}
@Override
- public void onProviderDisabled(@NotNull final String provider) {}
+ public void onProviderDisabled(@NonNull final String provider) {}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
index 82b6d9bb8..29d9b7a9e 100644
--- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
@@ -71,6 +71,7 @@ import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.google.android.material.textfield.TextInputLayout;
+import com.google.common.collect.Iterables;
import com.leinardi.android.speeddial.SpeedDialActionItem;
import com.leinardi.android.speeddial.SpeedDialView;
@@ -88,6 +89,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.stream.Collectors;
+import eu.siacs.conversations.BuildConfig;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.ActivityStartConversationBinding;
@@ -119,6 +121,8 @@ import eu.siacs.conversations.xmpp.XmppConnection;
public class StartConversationActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, CreatePrivateGroupChatDialog.CreateConferenceDialogListener, JoinConferenceDialog.JoinConferenceDialogListener, SwipeRefreshLayout.OnRefreshListener, CreatePublicChannelDialog.CreatePublicChannelDialogListener {
+ private static final String PREF_KEY_CONTACT_INTEGRATION_CONSENT = "contact_list_integration_consent";
+
public static final String EXTRA_INVITE_URI = "eu.siacs.conversations.invite_uri";
private final int REQUEST_SYNC_CONTACTS = 0x28cf;
@@ -248,7 +252,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
}
};
- public static void populateAccountSpinner(Context context, List accounts, Spinner spinner) {
+ public static void populateAccountSpinner(final Context context, final List accounts, final AutoCompleteTextView spinner) {
if (accounts.size() > 0) {
ArrayAdapter adapter = new ArrayAdapter<>(context, R.layout.simple_list_item, accounts);
adapter.setDropDownViewResource(R.layout.simple_list_item);
@@ -504,7 +508,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
}
Conversation conversation = xmppConnectionService.findOrCreateConversation(bookmark.getAccount(), jid, null, true, true, true, null);
bookmark.setConversation(conversation);
- if (!bookmark.autojoin() && getPreferences().getBoolean("autojoin", getResources().getBoolean(R.bool.autojoin))) {
+ if (!bookmark.autojoin()) {
bookmark.setAutojoin(true);
xmppConnectionService.createBookmark(bookmark.getAccount(), bookmark);
}
@@ -544,17 +548,27 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
}
protected void deleteConference() {
- int position = conference_context_id;
+ final int position = conference_context_id;
final Bookmark bookmark = (Bookmark) conferences.get(position);
+ final var conversation = bookmark.getConversation();
+ final boolean hasConversation = conversation != null;
+
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setNegativeButton(R.string.cancel, null);
builder.setTitle(R.string.delete_bookmark);
- builder.setMessage(JidDialog.style(this, R.string.remove_bookmark_text, bookmark.getJid().toEscapedString()));
- builder.setPositiveButton(R.string.delete, (dialog, which) -> {
+ if (hasConversation) {
+ builder.setMessage(JidDialog.style(this, R.string.remove_bookmark_and_close, bookmark.getJid().toEscapedString()));
+ } else {
+ builder.setMessage(JidDialog.style(this, R.string.remove_bookmark, bookmark.getJid().toEscapedString()));
+ }
+ builder.setPositiveButton(hasConversation ? R.string.delete_and_close : R.string.delete, (dialog, which) -> {
bookmark.setConversation(null);
final Account account = bookmark.getAccount();
xmppConnectionService.deleteBookmark(account, bookmark);
+ if (conversation != null) {
+ xmppConnectionService.archiveConversation(conversation);
+ }
filter(mSearchEditText.getText().toString());
});
builder.create().show();
@@ -680,18 +694,14 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
dialog.show(ft, FRAGMENT_TAG_DIALOG);
}
- public static Account getSelectedAccount(Context context, Spinner spinner) {
+ public static Account getSelectedAccount(final Context context, final AutoCompleteTextView spinner) {
if (spinner == null || !spinner.isEnabled()) {
return null;
}
if (context instanceof XmppActivity) {
- Jid jid;
+ final Jid jid;
try {
- if (Config.DOMAIN_LOCK != null) {
- jid = Jid.ofEscaped((String) spinner.getSelectedItem(), Config.DOMAIN_LOCK, null);
- } else {
- jid = Jid.ofEscaped((String) spinner.getSelectedItem());
- }
+ jid = Jid.ofEscaped(spinner.getText().toString());
} catch (final IllegalArgumentException e) {
return null;
}
@@ -735,12 +745,16 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
}
@Override
- public boolean onCreateOptionsMenu(Menu menu) {
+ public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.start_conversation, menu);
AccountUtils.showHideMenuItems(menu);
- MenuItem menuHideOffline = menu.findItem(R.id.action_hide_offline);
- MenuItem qrCodeScanMenuItem = menu.findItem(R.id.action_scan_qr_code);
+ final MenuItem menuHideOffline = menu.findItem(R.id.action_hide_offline);
+ final MenuItem qrCodeScanMenuItem = menu.findItem(R.id.action_scan_qr_code);
MenuItem noteToSelf = menu.findItem(R.id.action_note_to_self);
+ final MenuItem privacyPolicyMenuItem = menu.findItem(R.id.action_privacy_policy);
+ privacyPolicyMenuItem.setVisible(
+ BuildConfig.PRIVACY_POLICY != null
+ && QuickConversationsService.isPlayStoreFlavor());
qrCodeScanMenuItem.setVisible(isCameraFeatureAvailable());
if (QuickConversationsService.isQuicksy()) {
menuHideOffline.setVisible(false);
@@ -854,49 +868,95 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
}
private void askForContactsPermissions() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- if (checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
+ if (QuickConversationsService.isContactListIntegration(this)) {
+ if (checkSelfPermission(Manifest.permission.READ_CONTACTS)
+ != PackageManager.PERMISSION_GRANTED) {
if (mRequestedContactsPermission.compareAndSet(false, true)) {
- if (QuickConversationsService.isQuicksy() || shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) {
- final AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ final String consent =
+ PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
+ .getString(PREF_KEY_CONTACT_INTEGRATION_CONSENT, null);
+ final boolean requiresConsent =
+ (QuickConversationsService.isQuicksy()
+ || QuickConversationsService.isPlayStoreFlavor())
+ && !"agreed".equals(consent);
+ if (requiresConsent && "declined".equals(consent)) {
+ Log.d(Config.LOGTAG,"not asking for contacts permission because consent has been declined");
+ return;
+ }
+ if (requiresConsent
+ || shouldShowRequestPermissionRationale(
+ Manifest.permission.READ_CONTACTS)) {
+ final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
final AtomicBoolean requestPermission = new AtomicBoolean(false);
- builder.setTitle(R.string.sync_with_contacts);
if (QuickConversationsService.isQuicksy()) {
- builder.setMessage(Html.fromHtml(getString(R.string.sync_with_contacts_quicksy_static)));
+ builder.setTitle(R.string.quicksy_wants_your_consent);
+ builder.setMessage(
+ Html.fromHtml(
+ getString(R.string.sync_with_contacts_quicksy_static)));
} else {
- builder.setMessage(getString(R.string.sync_with_contacts_long, getString(R.string.app_name)));
+ builder.setTitle(R.string.sync_with_contacts);
+ builder.setMessage(
+ getString(
+ R.string.sync_with_contacts_long,
+ getString(R.string.app_name)));
}
@StringRes int confirmButtonText;
- if (QuickConversationsService.isConversations()) {
- confirmButtonText = R.string.next;
- } else {
+ if (requiresConsent) {
confirmButtonText = R.string.agree_and_continue;
+ } else {
+ confirmButtonText = R.string.next;
}
- builder.setPositiveButton(confirmButtonText, (dialog, which) -> {
- if (requestPermission.compareAndSet(false, true)) {
- requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
- }
- });
- builder.setOnDismissListener(dialog -> {
- if (QuickConversationsService.isConversations() && requestPermission.compareAndSet(false, true)) {
- requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
- }
- });
- if (QuickConversationsService.isQuicksy()) {
- builder.setNegativeButton(R.string.decline, null);
+ builder.setPositiveButton(
+ confirmButtonText,
+ (dialog, which) -> {
+ if (requiresConsent) {
+ PreferenceManager.getDefaultSharedPreferences(
+ getApplicationContext())
+ .edit()
+ .putString(
+ PREF_KEY_CONTACT_INTEGRATION_CONSENT, "agreed")
+ .apply();
+ }
+ if (requestPermission.compareAndSet(false, true)) {
+ requestPermissions(
+ new String[] {Manifest.permission.READ_CONTACTS},
+ REQUEST_SYNC_CONTACTS);
+ }
+ });
+ if (requiresConsent) {
+ builder.setNegativeButton(R.string.decline, (dialog, which) -> PreferenceManager.getDefaultSharedPreferences(
+ getApplicationContext())
+ .edit()
+ .putString(
+ PREF_KEY_CONTACT_INTEGRATION_CONSENT, "declined")
+ .apply());
+ } else {
+ builder.setOnDismissListener(
+ dialog -> {
+ if (requestPermission.compareAndSet(false, true)) {
+ requestPermissions(
+ new String[] {
+ Manifest.permission.READ_CONTACTS
+ },
+ REQUEST_SYNC_CONTACTS);
+ }
+ });
}
- builder.setCancelable(QuickConversationsService.isQuicksy());
+ builder.setCancelable(requiresConsent);
final AlertDialog dialog = builder.create();
- dialog.setCanceledOnTouchOutside(QuickConversationsService.isQuicksy());
- dialog.setOnShowListener(dialogInterface -> {
- final TextView tv = dialog.findViewById(android.R.id.message);
- if (tv != null) {
- tv.setMovementMethod(LinkMovementMethod.getInstance());
- }
- });
+ dialog.setCanceledOnTouchOutside(requiresConsent);
+ dialog.setOnShowListener(
+ dialogInterface -> {
+ final TextView tv = dialog.findViewById(android.R.id.message);
+ if (tv != null) {
+ tv.setMovementMethod(LinkMovementMethod.getInstance());
+ }
+ });
dialog.show();
} else {
- requestPermissions(new String[]{Manifest.permission.READ_CONTACTS}, REQUEST_SYNC_CONTACTS);
+ requestPermissions(
+ new String[] {Manifest.permission.READ_CONTACTS},
+ REQUEST_SYNC_CONTACTS);
}
}
}
@@ -932,8 +992,10 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
@Override
protected void onBackendConnected() {
-
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || checkSelfPermission(Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
+ if (QuickConversationsService.isContactListIntegration(this)
+ && (Build.VERSION.SDK_INT < Build.VERSION_CODES.M
+ || checkSelfPermission(Manifest.permission.READ_CONTACTS)
+ == PackageManager.PERMISSION_GRANTED)) {
xmppConnectionService.getQuickConversationsService().considerSyncBackground(false);
}
if (mPostponedActivityResult != null) {
@@ -1163,7 +1225,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
}
@Override
- public void onCreateDialogPositiveClick(Spinner spinner, String name) {
+ public void onCreateDialogPositiveClick(AutoCompleteTextView spinner, String name) {
if (!xmppConnectionServiceBound) {
return;
}
@@ -1181,7 +1243,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
}
@Override
- public void onJoinDialogPositiveClick(Dialog dialog, Spinner spinner, TextInputLayout layout, AutoCompleteTextView jid, boolean isBookmarkChecked) {
+ public void onJoinDialogPositiveClick(Dialog dialog, AutoCompleteTextView spinner, TextInputLayout layout, AutoCompleteTextView jid, boolean isBookmarkChecked) {
if (!xmppConnectionServiceBound) {
return;
}
@@ -1213,7 +1275,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
openConversationsForBookmark(bookmark);
} else {
bookmark = new Bookmark(account, conferenceJid.asBareJid());
- bookmark.setAutojoin(getBooleanPreference("autojoin", R.bool.autojoin));
+ bookmark.setAutojoin(true);
final String nick = conferenceJid.getResource();
if (nick != null && !nick.isEmpty() && !nick.equals(MucOptions.defaultNick(account))) {
bookmark.setNick(nick);
@@ -1332,7 +1394,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
}
@Override
- public void onCreateContextMenu(final ContextMenu menu, final View v, final ContextMenuInfo menuInfo) {
+ public void onCreateContextMenu(@NonNull final ContextMenu menu, @NonNull final View v, final ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
final StartConversationActivity activity = (StartConversationActivity) getActivity();
if (activity == null) {
@@ -1345,6 +1407,12 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
final Bookmark bookmark = (Bookmark) activity.conferences.get(acmi.position);
final Conversation conversation = bookmark.getConversation();
final MenuItem share = menu.findItem(R.id.context_share_uri);
+ final MenuItem delete = menu.findItem(R.id.context_delete_conference);
+ if (conversation != null) {
+ delete.setTitle(R.string.delete_and_close);
+ } else {
+ delete.setTitle(R.string.delete_bookmark);
+ }
share.setVisible(conversation == null || !conversation.isPrivateAndNonAnonymous());
} else if (mResContextMenu == R.menu.contact_context) {
activity.contact_context_id = acmi.position;
diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
index d5a95b7bf..ba069b9a5 100644
--- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
@@ -14,14 +14,7 @@ import android.widget.Toast;
import androidx.appcompat.app.ActionBar;
import androidx.databinding.DataBindingUtil;
-import org.whispersystems.libsignal.IdentityKey;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
+import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
@@ -40,6 +33,14 @@ import eu.siacs.conversations.utils.XmppUri;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
+import org.whispersystems.libsignal.IdentityKey;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdated {
private final Map ownKeysToTrust = new HashMap<>();
@@ -70,12 +71,14 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_trust_keys);
+ Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
this.contactJids = new ArrayList<>();
- for (String jid : getIntent().getStringArrayExtra("contacts")) {
+ final var intent = getIntent();
+ final String[] contacts = intent == null ? null : intent.getStringArrayExtra("contacts");
+ for (final String jid : (contacts == null ? new String[0] : contacts)) {
try {
this.contactJids.add(Jid.of(jid));
- } catch (IllegalArgumentException e) {
- e.printStackTrace();
+ } catch (final IllegalArgumentException ignored) {
}
}
@@ -100,7 +103,7 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.trust_keys, menu);
MenuItem scanQrCode = menu.findItem(R.id.action_scan_qr_code);
- scanQrCode.setVisible((ownKeysToTrust.size() > 0 || foreignActuallyHasKeys()) && isCameraFeatureAvailable());
+ scanQrCode.setVisible((!ownKeysToTrust.isEmpty() || foreignActuallyHasKeys()) && isCameraFeatureAvailable());
return super.onCreateOptionsMenu(menu);
}
@@ -152,7 +155,7 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
} else {
reloadFingerprints();
Log.d(Config.LOGTAG, "xmpp uri was: " + uri.getJid() + " has Fingerprints: " + uri.hasFingerprints());
- Toast.makeText(this, R.string.barcode_does_not_contain_fingerprints_for_this_conversation, Toast.LENGTH_SHORT).show();
+ Toast.makeText(this, R.string.barcode_does_not_contain_fingerprints_for_this_chat, Toast.LENGTH_SHORT).show();
}
populateView();
}
@@ -191,7 +194,7 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
}
);
}
- if (fingerprints.size() == 0) {
+ if (fingerprints.isEmpty()) {
keysCardBinding.noKeysToAccept.setVisibility(View.VISIBLE);
if (hasNoOtherTrustedKeys(jid)) {
if (!mAccount.getRoster().getContact(jid).mutualPresenceSubscription()) {
@@ -254,8 +257,8 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
}
}
- private void disableEncryptionDialog(View view) {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ private void disableEncryptionDialog(final View view) {
+ final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
builder.setTitle(R.string.disable_encryption);
builder.setMessage(R.string.disable_encryption_message);
builder.setPositiveButton(R.string.disable_now, (dialog, which) -> {
@@ -279,7 +282,7 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
private boolean foreignActuallyHasKeys() {
synchronized (this.foreignKeysToTrust) {
for (Map.Entry> entry : foreignKeysToTrust.entrySet()) {
- if (entry.getValue().size() > 0) {
+ if (!entry.getValue().isEmpty()) {
return true;
}
}
@@ -305,7 +308,7 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
foreignKeysToTrust.clear();
for (Jid jid : contactJids) {
Set foreignKeysSet = service.getKeysWithTrust(FingerprintStatus.createActiveUndecided(), jid);
- if (hasNoOtherTrustedKeys(jid) && ownKeysSet.size() == 0) {
+ if (hasNoOtherTrustedKeys(jid) && ownKeysSet.isEmpty()) {
foreignKeysSet.addAll(service.getKeysWithTrust(FingerprintStatus.createActive(false), jid));
}
Map foreignFingerprints = new HashMap<>();
@@ -315,7 +318,7 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
foreignFingerprints.put(fingerprint, false);
}
}
- if (foreignFingerprints.size() > 0 || !acceptedTargets.contains(jid)) {
+ if (!foreignFingerprints.isEmpty() || !acceptedTargets.contains(jid)) {
foreignKeysToTrust.put(jid, foreignFingerprints);
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java
index b1b91c99c..199847181 100644
--- a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java
@@ -11,8 +11,8 @@ import android.util.Log;
import android.view.View;
import android.widget.Toast;
+import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
-import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.databinding.DataBindingUtil;
@@ -36,14 +36,12 @@ import okhttp3.HttpUrl;
import okhttp3.Request;
import okhttp3.Response;
-import org.jetbrains.annotations.NotNull;
-
import java.io.IOException;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-public class UriHandlerActivity extends AppCompatActivity {
+public class UriHandlerActivity extends BaseActivity {
public static final String ACTION_SCAN_QR_CODE = "scan_qr_code";
private static final String EXTRA_ALLOW_PROVISIONING = "extra_allow_provisioning";
@@ -226,13 +224,13 @@ public class UriHandlerActivity extends AppCompatActivity {
this.call.enqueue(
new Callback() {
@Override
- public void onFailure(@NotNull Call call, @NotNull IOException e) {
+ public void onFailure(@NonNull Call call, @NonNull IOException e) {
Log.d(Config.LOGTAG, "unable to check HTTP url", e);
showError(R.string.no_xmpp_adddress_found);
}
@Override
- public void onResponse(@NotNull Call call, @NotNull Response response) {
+ public void onResponse(@NonNull Call call, @NonNull Response response) {
if (response.isSuccessful()) {
final String linkHeader = response.header("Link");
if (linkHeader != null && processLinkHeader(linkHeader)) {
diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
index e817b760f..82b2f4e08 100644
--- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
@@ -2,7 +2,6 @@ package eu.siacs.conversations.ui;
import android.Manifest;
import android.annotation.SuppressLint;
-import android.annotation.TargetApi;
import android.app.PendingIntent;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
@@ -17,10 +16,9 @@ import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
import android.content.res.Resources;
-import android.content.res.TypedArray;
import android.graphics.Bitmap;
-import android.graphics.Color;
import android.graphics.Point;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
@@ -63,6 +61,8 @@ import java.util.List;
import java.util.Objects;
import java.util.concurrent.RejectedExecutionException;
+import eu.siacs.conversations.AppSettings;
+import eu.siacs.conversations.BuildConfig;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.PgpEngine;
@@ -161,7 +161,6 @@ public abstract class XmppActivity extends ActionBarActivity {
}
};
- public boolean mSkipBackgroundBinding = false;
public static boolean cancelPotentialWork(Message message, ImageView imageView) {
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
@@ -219,14 +218,10 @@ public abstract class XmppActivity extends ActionBarActivity {
abstract protected void refreshUiReal();
@Override
- protected void onStart() {
+ public void onStart() {
super.onStart();
if (!xmppConnectionServiceBound) {
- if (this.mSkipBackgroundBinding) {
- Log.d(Config.LOGTAG, "skipping background binding");
- } else {
- connectToBackend();
- }
+ connectToBackend();
} else {
this.registerListeners();
this.onBackendConnected();
@@ -366,7 +361,7 @@ public abstract class XmppActivity extends ActionBarActivity {
dialog.show();
}
- abstract void onBackendConnected();
+ protected abstract void onBackendConnected();
protected void registerListeners() {
if (this instanceof XmppConnectionService.OnConversationUpdate) {
@@ -432,7 +427,10 @@ public abstract class XmppActivity extends ActionBarActivity {
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.action_settings:
- startActivity(new Intent(this, SettingsActivity.class));
+ startActivity(new Intent(this, eu.siacs.conversations.ui.activity.SettingsActivity.class));
+ break;
+ case R.id.action_privacy_policy:
+ openPrivacyPolicy();
break;
case R.id.action_accounts:
AccountUtils.launchManageAccounts(this);
@@ -450,6 +448,20 @@ public abstract class XmppActivity extends ActionBarActivity {
return super.onOptionsItemSelected(item);
}
+ private void openPrivacyPolicy() {
+ if (BuildConfig.PRIVACY_POLICY == null) {
+ return;
+ }
+ final var viewPolicyIntent = new Intent(Intent.ACTION_VIEW);
+ viewPolicyIntent.setData(Uri.parse(BuildConfig.PRIVACY_POLICY));
+ try {
+ startActivity(viewPolicyIntent);
+ } catch (final ActivityNotFoundException e) {
+ Toast.makeText(this, R.string.no_application_found_to_open_link, Toast.LENGTH_SHORT)
+ .show();
+ }
+ }
+
public void selectPresence(final Conversation conversation, final PresenceSelector.OnPresenceSelected listener) {
final Contact contact = conversation.getContact();
if (contact.showInRoster() || contact.isSelf()) {
@@ -486,7 +498,6 @@ public abstract class XmppActivity extends ActionBarActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
metrics = getResources().getDisplayMetrics();
- ExceptionHelper.init(getApplicationContext());
EmojiInitializationService.execute(this);
this.isCameraFeatureAvailable = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY);
this.mTheme = findTheme();
@@ -741,10 +752,10 @@ public abstract class XmppActivity extends ActionBarActivity {
}
}
- protected void choosePgpSignId(Account account) {
- xmppConnectionService.getPgpEngine().chooseKey(account, new UiCallback() {
+ protected void choosePgpSignId(final Account account) {
+ xmppConnectionService.getPgpEngine().chooseKey(account, new UiCallback<>() {
@Override
- public void success(Account account1) {
+ public void success(final Account a) {
}
@Override
@@ -861,7 +872,7 @@ public abstract class XmppActivity extends ActionBarActivity {
}
protected boolean hasStoragePermission(int requestCode) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode);
return false;
@@ -899,7 +910,7 @@ public abstract class XmppActivity extends ActionBarActivity {
}
protected boolean manuallyChangePresence() {
- return getBooleanPreference(SettingsActivity.MANUALLY_CHANGE_PRESENCE, R.bool.manually_change_presence);
+ return getBooleanPreference(AppSettings.MANUALLY_CHANGE_PRESENCE, R.bool.manually_change_presence);
}
protected String getShareableUri() {
@@ -940,7 +951,7 @@ public abstract class XmppActivity extends ActionBarActivity {
@Override
protected void onResume(){
super.onResume();
- SettingsUtils.applyScreenshotPreventionSetting(this);
+ SettingsUtils.applyScreenshotSetting(this);
}
protected int findTheme() {
@@ -968,7 +979,7 @@ public abstract class XmppActivity extends ActionBarActivity {
if (uri == null || uri.isEmpty()) {
return;
}
- Point size = new Point();
+ final Point size = new Point();
getWindowManager().getDefaultDisplay().getSize(size);
final int width = (size.x < size.y ? size.x : size.y);
Bitmap bitmap = BarcodeProvider.create2dBarcodeBitmap(uri, width);
@@ -1039,14 +1050,15 @@ public abstract class XmppActivity extends ActionBarActivity {
return invite;
}
- public boolean execute(XmppActivity activity) {
- XmppConnectionService service = activity.xmppConnectionService;
- Conversation conversation = service.findConversationByUuid(this.uuid);
+ public boolean execute(final XmppActivity activity) {
+ final XmppConnectionService service = activity.xmppConnectionService;
+ final Conversation conversation = service.findConversationByUuid(this.uuid);
if (conversation == null) {
return false;
}
if (conversation.getMode() == Conversation.MODE_MULTI) {
- for (Jid jid : jids) {
+ for (final Jid jid : jids) {
+ // TODO use direct invites for public conferences
service.invite(conversation, jid);
}
return false;
diff --git a/src/main/java/eu/siacs/conversations/ui/activity/SettingsActivity.java b/src/main/java/eu/siacs/conversations/ui/activity/SettingsActivity.java
new file mode 100644
index 000000000..7ac855534
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/ui/activity/SettingsActivity.java
@@ -0,0 +1,70 @@
+package eu.siacs.conversations.ui.activity;
+
+import android.app.Notification;
+import android.os.Bundle;
+
+import androidx.databinding.DataBindingUtil;
+import androidx.preference.PreferenceFragmentCompat;
+
+import com.google.common.collect.ImmutableSet;
+
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.databinding.ActivitySettingsBinding;
+import eu.siacs.conversations.ui.Activities;
+import eu.siacs.conversations.ui.XmppActivity;
+import eu.siacs.conversations.ui.fragment.settings.MainSettingsFragment;
+import eu.siacs.conversations.ui.fragment.settings.NotificationsSettingsFragment;
+import eu.siacs.conversations.ui.fragment.settings.XmppPreferenceFragment;
+
+import java.util.Collections;
+
+public class SettingsActivity extends XmppActivity {
+
+ @Override
+ protected void refreshUiReal() {}
+
+ @Override
+ protected void onBackendConnected() {
+ final var fragmentManager = getSupportFragmentManager();
+ final var currentFragment = fragmentManager.findFragmentById(R.id.fragment_container);
+ if (currentFragment instanceof XmppPreferenceFragment xmppPreferenceFragment) {
+ xmppPreferenceFragment.onBackendConnected();
+ }
+ }
+
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ final ActivitySettingsBinding binding =
+ DataBindingUtil.setContentView(this, R.layout.activity_settings);
+ setSupportActionBar(binding.materialToolbar);
+ Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
+
+ final var intent = getIntent();
+ final var categories = intent == null ? Collections.emptySet() : intent.getCategories();
+ final PreferenceFragmentCompat preferenceFragment;
+ if (ImmutableSet.of(Notification.INTENT_CATEGORY_NOTIFICATION_PREFERENCES)
+ .equals(categories)) {
+ preferenceFragment = new NotificationsSettingsFragment();
+ } else {
+ preferenceFragment = new MainSettingsFragment();
+ }
+
+ final var fragmentManager = getSupportFragmentManager();
+ final var currentFragment = fragmentManager.findFragmentById(R.id.fragment_container);
+ if (currentFragment == null) {
+ fragmentManager
+ .beginTransaction()
+ .replace(R.id.fragment_container, preferenceFragment)
+ .commit();
+ }
+ binding.materialToolbar.setNavigationOnClickListener(
+ view -> {
+ if (fragmentManager.getBackStackEntryCount() == 0) {
+ finish();
+ } else {
+ fragmentManager.popBackStack();
+ }
+ });
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/ui/activity/result/PickRingtone.java b/src/main/java/eu/siacs/conversations/ui/activity/result/PickRingtone.java
new file mode 100644
index 000000000..975ba9431
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/ui/activity/result/PickRingtone.java
@@ -0,0 +1,51 @@
+package eu.siacs.conversations.ui.activity.result;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.media.RingtoneManager;
+import android.net.Uri;
+
+import androidx.activity.result.contract.ActivityResultContract;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class PickRingtone extends ActivityResultContract {
+
+ private static final Uri NONE = Uri.parse("about:blank");
+
+ private final int ringToneType;
+
+ public PickRingtone(final int ringToneType) {
+ this.ringToneType = ringToneType;
+ }
+
+ @NonNull
+ @Override
+ public Intent createIntent(@NonNull final Context context, final Uri existing) {
+ final Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER);
+ intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, ringToneType);
+ intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, true);
+ intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, true);
+ if (noneToNull(existing) != null) {
+ intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, existing);
+ }
+ return intent;
+ }
+
+ @Override
+ public Uri parseResult(int resultCode, @Nullable Intent data) {
+ if (resultCode != Activity.RESULT_OK || data == null) {
+ return null;
+ }
+ return nullToNone(data.getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI));
+ }
+
+ public static Uri noneToNull(final Uri uri) {
+ return uri == null || NONE.equals(uri) ? null : uri;
+ }
+
+ public static @NonNull Uri nullToNone(final Uri uri) {
+ return uri == null ? NONE : uri;
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java
index 038f255ea..88475c580 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/AccountAdapter.java
@@ -8,15 +8,16 @@ import android.widget.ArrayAdapter;
import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil;
-import java.util.List;
+import com.google.android.material.color.MaterialColors;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
-import eu.siacs.conversations.databinding.AccountRowBinding;
+import eu.siacs.conversations.databinding.ItemAccountBinding;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.ui.XmppActivity;
import eu.siacs.conversations.ui.util.AvatarWorkerTask;
-import eu.siacs.conversations.ui.util.StyledAttributes;
+
+import java.util.List;
public class AccountAdapter extends ArrayAdapter {
@@ -35,36 +36,33 @@ public class AccountAdapter extends ArrayAdapter {
this.showStateButton = true;
}
+ @NonNull
@Override
public View getView(int position, View view, @NonNull ViewGroup parent) {
final Account account = getItem(position);
final ViewHolder viewHolder;
if (view == null) {
- AccountRowBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.account_row, parent, false);
+ ItemAccountBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.item_account, parent, false);
view = binding.getRoot();
viewHolder = new ViewHolder(binding);
view.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) view.getTag();
}
- if (Config.DOMAIN_LOCK != null) {
- viewHolder.binding.accountJid.setText(account.getJid().getLocal());
- } else {
- viewHolder.binding.accountJid.setText(account.getJid().asBareJid().toEscapedString());
- }
+ viewHolder.binding.accountJid.setText(account.getJid().asBareJid().toEscapedString());
AvatarWorkerTask.loadAvatar(account, viewHolder.binding.accountImage, R.dimen.avatar);
viewHolder.binding.accountStatus.setText(getContext().getString(account.getStatus().getReadableId()));
switch (account.getStatus()) {
case ONLINE:
- viewHolder.binding.accountStatus.setTextColor(StyledAttributes.getColor(activity, R.attr.TextColorOnline));
+ viewHolder.binding.accountStatus.setTextColor(MaterialColors.getColor(viewHolder.binding.accountStatus, com.google.android.material.R.attr.colorPrimary));
break;
case DISABLED:
case LOGGED_OUT:
case CONNECTING:
- viewHolder.binding.accountStatus.setTextColor(StyledAttributes.getColor(activity, android.R.attr.textColorSecondary));
+ viewHolder.binding.accountStatus.setTextColor(MaterialColors.getColor(viewHolder.binding.accountStatus, com.google.android.material.R.attr.colorOnSurfaceVariant));
break;
default:
- viewHolder.binding.accountStatus.setTextColor(StyledAttributes.getColor(activity, R.attr.TextColorError));
+ viewHolder.binding.accountStatus.setTextColor(MaterialColors.getColor(viewHolder.binding.accountStatus, com.google.android.material.R.attr.colorError));
break;
}
final boolean isDisabled = (account.getStatus() == Account.State.DISABLED);
@@ -84,9 +82,9 @@ public class AccountAdapter extends ArrayAdapter {
}
private static class ViewHolder {
- private final AccountRowBinding binding;
+ private final ItemAccountBinding binding;
- private ViewHolder(AccountRowBinding binding) {
+ private ViewHolder(ItemAccountBinding binding) {
this.binding = binding;
}
}
diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ChannelSearchResultAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ChannelSearchResultAdapter.java
index ae9a67f6b..aa6fd1b78 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/ChannelSearchResultAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/ChannelSearchResultAdapter.java
@@ -13,18 +13,18 @@ import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
-import java.util.Locale;
-
import eu.siacs.conversations.R;
-import eu.siacs.conversations.databinding.SearchResultItemBinding;
+import eu.siacs.conversations.databinding.ItemChannelDiscoveryBinding;
import eu.siacs.conversations.entities.Room;
import eu.siacs.conversations.ui.XmppActivity;
import eu.siacs.conversations.ui.util.AvatarWorkerTask;
import eu.siacs.conversations.xmpp.Jid;
+import java.util.Locale;
+
public class ChannelSearchResultAdapter extends ListAdapter implements View.OnCreateContextMenuListener {
- private static final DiffUtil.ItemCallback DIFF = new DiffUtil.ItemCallback() {
+ private static final DiffUtil.ItemCallback DIFF = new DiffUtil.ItemCallback<>() {
@Override
public boolean areItemsTheSame(@NonNull Room a, @NonNull Room b) {
return a.address != null && a.address.equals(b.address);
@@ -45,7 +45,7 @@ public class ChannelSearchResultAdapter extends ListAdapter {
@@ -347,9 +352,40 @@ public class ConversationAdapter
StyledAttributes.getColor(activity, R.attr.color_background_primary));
}
- Message message = conversation.getLatestMessage();
+ final Message message = conversation.getLatestMessage();
+ final int status = message.getStatus();
final int unreadCount = conversation.unreadCount();
final boolean isRead = conversation.isRead();
+ final @DrawableRes Integer messageStatusDrawable =
+ MessageAdapter.getMessageStatusAsDrawable(message, status);
+ if (message.getType() == Message.TYPE_RTP_SESSION) {
+ viewHolder.binding.messageStatus.setVisibility(View.GONE);
+ } else if (messageStatusDrawable == null) {
+ if (status <= Message.STATUS_RECEIVED) {
+ viewHolder.binding.messageStatus.setVisibility(View.GONE);
+ } else {
+ viewHolder.binding.messageStatus.setVisibility(View.INVISIBLE);
+ }
+ } else {
+ viewHolder.binding.messageStatus.setImageResource(messageStatusDrawable);
+ if (status == Message.STATUS_SEND_DISPLAYED) {
+ viewHolder.binding.messageStatus.setImageResource(R.drawable.ic_done_all_bold_24dp);
+ ImageViewCompat.setImageTintList(
+ viewHolder.binding.messageStatus,
+ ColorStateList.valueOf(
+ MaterialColors.getColor(
+ viewHolder.binding.messageStatus,
+ com.google.android.material.R.attr.colorPrimary)));
+ } else {
+ ImageViewCompat.setImageTintList(
+ viewHolder.binding.messageStatus,
+ ColorStateList.valueOf(
+ MaterialColors.getColor(
+ viewHolder.binding.messageStatus,
+ com.google.android.material.R.attr.colorControlNormal)));
+ }
+ viewHolder.binding.messageStatus.setVisibility(View.VISIBLE);
+ }
final Conversation.Draft draft = isRead ? conversation.getDraft() : null;
if (unreadCount > 0) {
viewHolder.binding.unreadCount.setVisibility(View.VISIBLE);
@@ -385,63 +421,12 @@ public class ConversationAdapter
R.attr.ic_attach_location, R.drawable.ic_attach_location);
showPreviewText = false;
} else {
- // TODO move this into static MediaPreview method and use same icons as in
- // MediaAdapter
- final String mime = message.getMimeType();
- if (MimeUtils.AMBIGUOUS_CONTAINER_FORMATS.contains(mime)) {
- final Message.FileParams fileParams = message.getFileParams();
- if (fileParams.width > 0 && fileParams.height > 0) {
- imageResource =
- activity.getThemeResource(
- R.attr.ic_attach_videocam,
- R.drawable.ic_attach_videocam);
- showPreviewText = false;
- } else if (fileParams.runtime > 0) {
- imageResource =
- activity.getThemeResource(
- R.attr.ic_attach_record, R.drawable.ic_attach_record);
- showPreviewText = false;
- } else {
- imageResource =
- activity.getThemeResource(
- R.attr.ic_attach_document,
- R.drawable.ic_attach_document);
- showPreviewText = true;
- }
- } 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;
- }
- }
+ final var attachment = Attachment.of(message);
+ imageResource = MediaAdapter.getImageDrawable(attachment);
+ showPreviewText = false;
+ viewHolder.binding.conversationLastmsgImg.setImageResource(imageResource);
+ viewHolder.binding.conversationLastmsgImg.setVisibility(View.VISIBLE);
}
- viewHolder.binding.conversationLastmsgImg.setImageResource(imageResource);
- viewHolder.binding.conversationLastmsgImg.setVisibility(View.VISIBLE);
} else {
viewHolder.binding.conversationLastmsgImg.setVisibility(View.GONE);
showPreviewText = true;
@@ -475,7 +460,7 @@ public class ConversationAdapter
viewHolder.binding.senderName.setTypeface(null, Typeface.BOLD);
}
}
- if (message.getStatus() == Message.STATUS_RECEIVED) {
+ if (status == Message.STATUS_RECEIVED) {
if (conversation.getMode() == Conversation.MODE_MULTI) {
viewHolder.binding.senderName.setVisibility(View.VISIBLE);
viewHolder.binding.senderName.setText(
@@ -623,9 +608,9 @@ public class ConversationAdapter
}
static class ConversationViewHolder extends RecyclerView.ViewHolder {
- private final ConversationListRowBinding binding;
+ private final ItemConversationBinding binding;
- private ConversationViewHolder(ConversationListRowBinding binding) {
+ private ConversationViewHolder(final ItemConversationBinding binding) {
super(binding.getRoot());
this.binding = binding;
binding.getRoot().setLongClickable(true);
diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java
index 08f8fcd32..b085cb964 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java
@@ -6,32 +6,35 @@ import android.widget.Filter;
import androidx.annotation.NonNull;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Locale;
-import java.util.regex.Pattern;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Ordering;
import eu.siacs.conversations.Config;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.regex.Pattern;
+
public class KnownHostsAdapter extends ArrayAdapter {
private static final Pattern E164_PATTERN = Pattern.compile("^\\+[1-9]\\d{1,14}$");
- private ArrayList domains;
+ private List domains;
private final Filter domainFilter = new Filter() {
@Override
- protected FilterResults performFiltering(CharSequence constraint) {
- final ArrayList suggestions = new ArrayList<>();
+ protected FilterResults performFiltering(final CharSequence constraint) {
+ final ImmutableList.Builder builder = new ImmutableList.Builder<>();
final String[] split = constraint == null ? new String[0] : constraint.toString().split("@");
if (split.length == 1) {
final String local = split[0].toLowerCase(Locale.ENGLISH);
if (Config.QUICKSY_DOMAIN != null && E164_PATTERN.matcher(local).matches()) {
- suggestions.add(local + '@' + Config.QUICKSY_DOMAIN.toEscapedString());
+ builder.add(local + '@' + Config.QUICKSY_DOMAIN.toEscapedString());
} else {
for (String domain : domains) {
- suggestions.add(local + '@' + domain);
+ builder.add(local + '@' + domain);
}
}
} else if (split.length == 2) {
@@ -40,45 +43,49 @@ public class KnownHostsAdapter extends ArrayAdapter {
if (domains.contains(domainPart)) {
return new FilterResults();
}
- for (String domain : domains) {
+ for (final String domain : domains) {
if (domain.contains(domainPart)) {
- suggestions.add(localPart + "@" + domain);
+ builder.add(localPart + "@" + domain);
}
}
} else {
return new FilterResults();
}
- FilterResults filterResults = new FilterResults();
+ final var suggestions = builder.build();
+ final FilterResults filterResults = new FilterResults();
filterResults.values = suggestions;
filterResults.count = suggestions.size();
return filterResults;
}
@Override
- protected void publishResults(CharSequence constraint, FilterResults results) {
- ArrayList filteredList = (ArrayList) results.values;
- if (results.count > 0) {
- clear();
- addAll(filteredList);
- notifyDataSetChanged();
+ protected void publishResults(final CharSequence constraint, final FilterResults results) {
+ final ImmutableList.Builder suggestions = new ImmutableList.Builder<>();
+ if (results.values instanceof Collection> collection) {
+ for(final Object item : collection) {
+ if (item instanceof String string) {
+ suggestions.add(string);
+ }
+ }
}
+ clear();
+ addAll(suggestions.build());
+ notifyDataSetChanged();
}
};
- public KnownHostsAdapter(Context context, int viewResourceId, Collection mKnownHosts) {
+ public KnownHostsAdapter(final Context context, final int viewResourceId, final Collection knownHosts) {
super(context, viewResourceId, new ArrayList<>());
- domains = new ArrayList<>(mKnownHosts);
- Collections.sort(domains);
+ domains = Ordering.natural().sortedCopy(knownHosts);
}
- public KnownHostsAdapter(Context context, int viewResourceId) {
+ public KnownHostsAdapter(final Context context, final int viewResourceId) {
super(context, viewResourceId, new ArrayList<>());
- domains = new ArrayList<>();
+ domains = ImmutableList.of();
}
- public void refresh(Collection knownHosts) {
- domains = new ArrayList<>(knownHosts);
- Collections.sort(domains);
+ public void refresh(final Collection knownHosts) {
+ this.domains = Ordering.natural().sortedCopy(knownHosts);
notifyDataSetChanged();
}
diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java
index 5d6d72684..4f9311392 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/ListItemAdapter.java
@@ -1,7 +1,9 @@
package eu.siacs.conversations.ui.adapter;
import android.content.SharedPreferences;
+import android.content.res.ColorStateList;
import android.preference.PreferenceManager;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -9,30 +11,40 @@ import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.constraintlayout.helper.widget.Flow;
+import androidx.constraintlayout.widget.ConstraintLayout;
+import androidx.core.content.ContextCompat;
+import androidx.core.view.ViewCompat;
import androidx.databinding.DataBindingUtil;
-import com.wefika.flowlayout.FlowLayout;
-
-import java.util.List;
+import com.google.android.material.color.MaterialColors;
+import com.google.common.collect.ImmutableList;
+import com.google.common.primitives.Ints;
+import eu.siacs.conversations.AppSettings;
+import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
-import eu.siacs.conversations.databinding.ContactBinding;
+import eu.siacs.conversations.databinding.ItemContactBinding;
+import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.ListItem;
-import eu.siacs.conversations.ui.SettingsActivity;
+import eu.siacs.conversations.entities.Presence;
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.UIHelper;
+import eu.siacs.conversations.utils.XEP0392Helper;
import eu.siacs.conversations.xmpp.Jid;
+import java.util.List;
+
public class ListItemAdapter extends ArrayAdapter {
protected XmppActivity activity;
private boolean showDynamicTags = false;
private OnTagClickedListener mOnTagClickedListener = null;
private final View.OnClickListener onTagTvClick = view -> {
- if (view instanceof TextView && mOnTagClickedListener != null) {
- TextView tv = (TextView) view;
+ if (view instanceof TextView tv && mOnTagClickedListener != null) {
final String tag = tv.getText().toString();
mOnTagClickedListener.onTagClicked(tag);
}
@@ -46,36 +58,78 @@ public class ListItemAdapter extends ArrayAdapter {
public void refreshSettings() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity);
- this.showDynamicTags = preferences.getBoolean(SettingsActivity.SHOW_DYNAMIC_TAGS, false);
+ this.showDynamicTags = preferences.getBoolean(AppSettings.SHOW_DYNAMIC_TAGS, false);
}
+ @NonNull
@Override
- public View getView(int position, View view, ViewGroup parent) {
+ public View getView(int position, View view, @NonNull ViewGroup parent) {
LayoutInflater inflater = activity.getLayoutInflater();
- ListItem item = getItem(position);
+ final ListItem item = getItem(position);
ViewHolder viewHolder;
if (view == null) {
- ContactBinding binding = DataBindingUtil.inflate(inflater,R.layout.contact,parent,false);
+ final ItemContactBinding binding = DataBindingUtil.inflate(inflater,R.layout.item_contact,parent,false);
viewHolder = ViewHolder.get(binding);
view = binding.getRoot();
} else {
viewHolder = (ViewHolder) view.getTag();
}
- view.setBackground(StyledAttributes.getDrawable(view.getContext(),R.attr.list_item_background));
-
- List tags = item.getTags(activity);
- if (tags.size() == 0 || !this.showDynamicTags) {
+ if (view.isActivated()) {
+ Log.d(Config.LOGTAG,"item "+item.getDisplayName()+" is activated");
+ }
+ //view.setBackground(StyledAttributes.getDrawable(view.getContext(),R.attr.list_item_background));
+ final List tags = item.getTags(activity);
+ final boolean hasMetaTags;
+ if (item instanceof Contact contact) {
+ hasMetaTags = contact.isBlocked() || contact.getShownStatus() != Presence.Status.OFFLINE;
+ } else {
+ hasMetaTags = false;
+ }
+ if ((tags.isEmpty() && !hasMetaTags) || !this.showDynamicTags) {
viewHolder.tags.setVisibility(View.GONE);
} else {
viewHolder.tags.setVisibility(View.VISIBLE);
- viewHolder.tags.removeAllViewsInLayout();
- for (ListItem.Tag tag : tags) {
- TextView tv = (TextView) inflater.inflate(R.layout.list_item_tag, viewHolder.tags, false);
- tv.setText(tag.getName());
- tv.setBackgroundColor(tag.getColor());
+ viewHolder.tags.removeViews(1, viewHolder.tags.getChildCount() - 1);
+ final ImmutableList.Builder viewIdBuilder = new ImmutableList.Builder<>();
+ for (final ListItem.Tag tag : tags) {
+ final String name = tag.getName();
+ final TextView tv = (TextView) inflater.inflate(R.layout.list_item_tag, viewHolder.tags, false);
+ tv.setText(name);
+ tv.setBackgroundTintList(ColorStateList.valueOf(MaterialColors.harmonizeWithPrimary(getContext(),XEP0392Helper.rgbFromNick(name))));
tv.setOnClickListener(this.onTagTvClick);
+ final int id = ViewCompat.generateViewId();
+ tv.setId(id);
+ viewIdBuilder.add(id);
viewHolder.tags.addView(tv);
}
+ if (item instanceof Contact contact) {
+ if (contact.isBlocked()) {
+ final TextView tv =
+ (TextView)
+ inflater.inflate(
+ R.layout.list_item_tag, viewHolder.tags, false);
+ tv.setText(R.string.blocked);
+ tv.setBackgroundTintList(ColorStateList.valueOf(MaterialColors.harmonizeWithPrimary(tv.getContext(),ContextCompat.getColor(tv.getContext(),R.color.gray_800))));
+ final int id = ViewCompat.generateViewId();
+ tv.setId(id);
+ viewIdBuilder.add(id);
+ viewHolder.tags.addView(tv);
+ } else {
+ final Presence.Status status = contact.getShownStatus();
+ if (status != Presence.Status.OFFLINE) {
+ final TextView tv =
+ (TextView)
+ inflater.inflate(
+ R.layout.list_item_tag, viewHolder.tags, false);
+ UIHelper.setStatus(tv, status);
+ final int id = ViewCompat.generateViewId();
+ tv.setId(id);
+ viewIdBuilder.add(id);
+ viewHolder.tags.addView(tv);
+ }
+ }
+ }
+ viewHolder.flowWidget.setReferencedIds(Ints.toArray(viewIdBuilder.build()));
}
final Jid jid = item.getJid();
if (jid != null) {
@@ -102,18 +156,20 @@ public class ListItemAdapter extends ArrayAdapter {
private TextView name;
private TextView jid;
private ImageView avatar;
- private FlowLayout tags;
+ private ConstraintLayout tags;
+ private Flow flowWidget;
private ViewHolder() {
}
- public static ViewHolder get(ContactBinding binding) {
+ public static ViewHolder get(final ItemContactBinding binding) {
ViewHolder viewHolder = new ViewHolder();
viewHolder.name = binding.contactDisplayName;
viewHolder.jid = binding.contactJid;
viewHolder.avatar = binding.contactPhoto;
viewHolder.tags = binding.tags;
+ viewHolder.flowWidget = binding.flowWidget;
binding.getRoot().setTag(viewHolder);
return viewHolder;
}
diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java
index 2683876c7..7b3a35ab3 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java
@@ -1,47 +1,50 @@
package eu.siacs.conversations.ui.adapter;
-import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Bitmap;
+import android.graphics.Color;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.AsyncTask;
-import android.util.Log;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import android.widget.ImageView;
-import androidx.annotation.AttrRes;
import androidx.annotation.DimenRes;
+import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
+import androidx.core.widget.ImageViewCompat;
import androidx.databinding.DataBindingUtil;
import androidx.recyclerview.widget.RecyclerView;
+import com.google.android.material.color.MaterialColors;
+import com.google.common.base.Strings;
+
+import eu.siacs.conversations.R;
+import eu.siacs.conversations.databinding.ItemMediaBinding;
+import eu.siacs.conversations.ui.XmppActivity;
+import eu.siacs.conversations.ui.util.Attachment;
+import eu.siacs.conversations.ui.util.ViewUtil;
+import eu.siacs.conversations.worker.ExportBackupWorker;
+
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.RejectedExecutionException;
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.databinding.MediaBinding;
-import eu.siacs.conversations.services.ExportBackupService;
-import eu.siacs.conversations.ui.XmppActivity;
-import eu.siacs.conversations.ui.util.Attachment;
-import eu.siacs.conversations.ui.util.StyledAttributes;
-import eu.siacs.conversations.ui.util.ViewUtil;
-
public class MediaAdapter extends RecyclerView.Adapter {
- public static final List DOCUMENT_MIMES = Arrays.asList(
- "application/pdf",
- "application/vnd.oasis.opendocument.text",
- "application/msword",
- "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
- "text/x-tex",
- "text/plain"
- );
+ public static final List DOCUMENT_MIMES =
+ Arrays.asList(
+ "application/pdf",
+ "application/vnd.oasis.opendocument.text",
+ "application/msword",
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
+ "text/x-tex",
+ "text/plain");
+ public static final List CODE_MIMES = Arrays.asList("text/html", "text/xml");
private final ArrayList attachments = new ArrayList<>();
@@ -55,58 +58,77 @@ public class MediaAdapter extends RecyclerView.Adapter ViewUtil.view(activity, attachment));
}
- public void setAttachments(List attachments) {
+ public void setAttachments(final List attachments) {
this.attachments.clear();
this.attachments.addAll(attachments);
notifyDataSetChanged();
@@ -167,16 +188,21 @@ public class MediaAdapter extends RecyclerView.Adapter {
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.concurrent.RejectedExecutionException;
+
+public class MediaPreviewAdapter
+ extends RecyclerView.Adapter {
private final ArrayList mediaPreviews = new ArrayList<>();
@@ -43,8 +55,9 @@ public class MediaPreviewAdapter extends RecyclerView.Adapter {
- final int pos = mediaPreviews.indexOf(attachment);
- mediaPreviews.remove(pos);
- notifyItemRemoved(pos);
- conversationFragment.toggleInputMethod();
- });
+ holder.binding.deleteButton.setOnClickListener(
+ v -> {
+ final int pos = mediaPreviews.indexOf(attachment);
+ mediaPreviews.remove(pos);
+ notifyItemRemoved(pos);
+ conversationFragment.toggleInputMethod();
+ });
holder.binding.mediaPreview.setOnClickListener(v -> {
if (attachment.getType() == Attachment.Type.IMAGE) {
conversationFragment.editImage(attachment.getUri());
@@ -74,17 +88,27 @@ public class MediaPreviewAdapter extends RecyclerView.Adapter 0;
+ return !mediaPreviews.isEmpty();
}
public ArrayList getAttachments() {
@@ -176,9 +210,9 @@ public class MediaPreviewAdapter extends RecyclerView.Adapter