diff options
author | lookshe <github@lookshe.org> | 2016-03-06 19:42:55 +0100 |
---|---|---|
committer | lookshe <github@lookshe.org> | 2016-03-06 19:42:55 +0100 |
commit | 3c400703e082a1b180b35d891b8fb3460c7d5b87 (patch) | |
tree | 28738dd90fc41b4ab71897f38d324828778ad2e3 /src/main/java/de/thedevstack/conversationsplus/utils | |
parent | 72114d732427266024cdd6e27cd8d1aa60afae2f (diff) | |
parent | f28d77dc42f6bac5a026e0b1c78562dee8de45ac (diff) |
Merge branch 'trz/rebase' into trz/rename
Diffstat (limited to 'src/main/java/de/thedevstack/conversationsplus/utils')
19 files changed, 662 insertions, 177 deletions
diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/AvatarUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/AvatarUtil.java index 660100a3..206ee668 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/AvatarUtil.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/AvatarUtil.java @@ -16,8 +16,8 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import de.thedevstack.android.logcat.Logging; -import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.ConversationsPlusApplication; +import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.xmpp.pep.Avatar; /** diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/CryptoHelper.java b/src/main/java/de/thedevstack/conversationsplus/utils/CryptoHelper.java index 9599aa37..7e4a4fc5 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/CryptoHelper.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/CryptoHelper.java @@ -1,20 +1,35 @@ package de.thedevstack.conversationsplus.utils; -import java.security.SecureRandom; +import android.os.Bundle; +import android.util.Pair; + +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.asn1.x500.style.IETFUtils; +import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; + +import java.security.MessageDigest; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; import java.text.Normalizer; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.List; +import java.util.Locale; import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.R; +import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; +import de.thedevstack.conversationsplus.xmpp.jid.Jid; public final class CryptoHelper { public static final String FILETRANSFER = "?FILETRANSFERv1:"; private final static char[] hexArray = "0123456789abcdef".toCharArray(); - private final static char[] vowels = "aeiou".toCharArray(); - private final static char[] consonants = "bcdfghjklmnpqrstvwxyz".toCharArray(); final public static byte[] ONE = new byte[] { 0, 0, 0, 1 }; public static String bytesToHex(byte[] bytes) { @@ -48,22 +63,6 @@ public final class CryptoHelper { return result; } - public static String randomMucName(SecureRandom random) { - return randomWord(3, random) + "." + randomWord(7, random); - } - - private static String randomWord(int lenght, SecureRandom random) { - StringBuilder builder = new StringBuilder(lenght); - for (int i = 0; i < lenght; ++i) { - if (i % 2 == 0) { - builder.append(consonants[random.nextInt(consonants.length)]); - } else { - builder.append(vowels[random.nextInt(vowels.length)]); - } - } - return builder.toString(); - } - /** * Escapes usernames or passwords for SASL. */ @@ -96,11 +95,18 @@ public final class CryptoHelper { } else if (fingerprint.length() < 40) { return fingerprint; } + StringBuilder builder = new StringBuilder(fingerprint.toLowerCase(Locale.US).replaceAll("\\s", "")); + for(int i=8;i<builder.length();i+=9) { + builder.insert(i, ' '); + } + return builder.toString(); + } + + public static String prettifyFingerprintCert(String fingerprint) { StringBuilder builder = new StringBuilder(fingerprint); - builder.insert(8, " "); - builder.insert(17, " "); - builder.insert(26, " "); - builder.insert(35, " "); + for(int i=2;i < builder.length(); i+=3) { + builder.insert(i,':'); + } return builder.toString(); } @@ -126,4 +132,80 @@ public final class CryptoHelper { } } } + + public static Pair<Jid,String> extractJidAndName(X509Certificate certificate) throws CertificateEncodingException, InvalidJidException, CertificateParsingException { + Collection<List<?>> alternativeNames = certificate.getSubjectAlternativeNames(); + List<String> emails = new ArrayList<>(); + if (alternativeNames != null) { + for(List<?> san : alternativeNames) { + Integer type = (Integer) san.get(0); + if (type == 1) { + emails.add((String) san.get(1)); + } + } + } + X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject(); + if (emails.size() == 0) { + emails.add(IETFUtils.valueToString(x500name.getRDNs(BCStyle.EmailAddress)[0].getFirst().getValue())); + } + String name = IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[0].getFirst().getValue()); + if (emails.size() >= 1) { + return new Pair<>(Jid.fromString(emails.get(0)), name); + } else { + return null; + } + } + + public static Bundle extractCertificateInformation(X509Certificate certificate) { + Bundle information = new Bundle(); + try { + JcaX509CertificateHolder holder = new JcaX509CertificateHolder(certificate); + X500Name subject = holder.getSubject(); + try { + information.putString("subject_cn", subject.getRDNs(BCStyle.CN)[0].getFirst().getValue().toString()); + } catch (Exception e) { + //ignored + } + try { + information.putString("subject_o",subject.getRDNs(BCStyle.O)[0].getFirst().getValue().toString()); + } catch (Exception e) { + //ignored + } + + X500Name issuer = holder.getIssuer(); + try { + information.putString("issuer_cn", issuer.getRDNs(BCStyle.CN)[0].getFirst().getValue().toString()); + } catch (Exception e) { + //ignored + } + try { + information.putString("issuer_o", issuer.getRDNs(BCStyle.O)[0].getFirst().getValue().toString()); + } catch (Exception e) { + //ignored + } + try { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] fingerprint = md.digest(certificate.getEncoded()); + information.putString("sha1", prettifyFingerprintCert(bytesToHex(fingerprint))); + } catch (Exception e) { + + } + return information; + } catch (CertificateEncodingException e) { + return information; + } + } + + public static int encryptionTypeToText(int encryption) { + switch (encryption) { + case Message.ENCRYPTION_OTR: + return R.string.encryption_choice_otr; + case Message.ENCRYPTION_AXOLOTL: + return R.string.encryption_choice_omemo; + case Message.ENCRYPTION_NONE: + return R.string.encryption_choice_unencrypted; + default: + return R.string.encryption_choice_pgp; + } + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/DNSHelper.java b/src/main/java/de/thedevstack/conversationsplus/utils/DNSHelper.java index 9481572d..90618292 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/DNSHelper.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/DNSHelper.java @@ -1,33 +1,50 @@ package de.thedevstack.conversationsplus.utils; -import de.measite.minidns.Client; -import de.measite.minidns.DNSMessage; -import de.measite.minidns.Record; -import de.measite.minidns.Record.TYPE; -import de.measite.minidns.Record.CLASS; -import de.measite.minidns.record.SRV; -import de.measite.minidns.record.Data; -import de.measite.minidns.util.NameUtil; +import android.annotation.TargetApi; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.LinkProperties; +import android.net.Network; +import android.net.RouteInfo; +import android.os.Build; import java.io.IOException; import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.List; import java.util.TreeSet; import java.util.regex.Pattern; +import de.measite.minidns.Client; +import de.measite.minidns.DNSMessage; +import de.measite.minidns.Record; +import de.measite.minidns.Record.CLASS; +import de.measite.minidns.Record.TYPE; +import de.measite.minidns.record.Data; +import de.measite.minidns.record.SRV; +import de.measite.minidns.util.NameUtil; import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.ConversationsPlusApplication; import de.thedevstack.conversationsplus.dto.SrvRecord; +import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.xmpp.jid.Jid; public class DNSHelper { private static final String CLIENT_SRV_PREFIX = "_xmpp-client._tcp."; - 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 String SECURE_CLIENT_SRV_PREFIX = "_xmpps-client._tcp."; + 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"); protected static Client client = new Client(); + static { + client.setTimeout(Config.PING_TIMEOUT * 1000); + } + /** * Queries the SRV record for the server JID. * This method uses all available Domain Name Servers. @@ -36,12 +53,13 @@ public class DNSHelper { */ public static final TreeSet<SrvRecord> querySrvRecord(Jid jid) { String host = jid.getDomainpart(); - String dns[] = client.findDNS(); TreeSet<SrvRecord> result = new TreeSet<>(); - if (dns != null) { - for (String dnsserver : dns) { - result = querySrvRecord(host, dnsserver); + final List<InetAddress> dnsServers = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP ? getDnsServers() : getDnsServersPreLollipop(); + + if (dnsServers != null) { + for (InetAddress dnsServer : dnsServers) { + result = querySrvRecord(host, dnsServer); if (!result.isEmpty()) { break; } @@ -51,51 +69,92 @@ public class DNSHelper { return result; } + @TargetApi(21) + private static List<InetAddress> getDnsServers() { + List<InetAddress> servers = new ArrayList<>(); + ConnectivityManager connectivityManager = (ConnectivityManager) ConversationsPlusApplication.getInstance().getSystemService(Context.CONNECTIVITY_SERVICE); + Network[] networks = connectivityManager == null ? null : connectivityManager.getAllNetworks(); + if (networks == null) { + return getDnsServersPreLollipop(); + } + for(int i = 0; i < networks.length; ++i) { + LinkProperties linkProperties = connectivityManager.getLinkProperties(networks[i]); + if (linkProperties != null) { + if (hasDefaultRoute(linkProperties)) { + servers.addAll(0, linkProperties.getDnsServers()); + } else { + servers.addAll(linkProperties.getDnsServers()); + } + } + } + if (servers.size() > 0) { + Logging.d("dns", "used lollipop variant to discover dns servers in " + networks.length + " networks"); + } + return servers.size() > 0 ? servers : getDnsServersPreLollipop(); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private static boolean hasDefaultRoute(LinkProperties linkProperties) { + for(RouteInfo route: linkProperties.getRoutes()) { + if (route.isDefaultRoute()) { + return true; + } + } + return false; + } + + private static List<InetAddress> getDnsServersPreLollipop() { + List<InetAddress> servers = new ArrayList<>(); + String[] dns = client.findDNS(); + for(int i = 0; i < dns.length; ++i) { + try { + servers.add(InetAddress.getByName(dns[i])); + } catch (UnknownHostException e) { + //ignore + } + } + return servers; + } + /** * Queries the SRV record for an host from the given Domain Name Server. * @param host the host to query for - * @param dnsserver the DNS to query on + * @param dnsServerAddress the DNS to query on * @return TreeSet with SrvRecords. */ - private static final TreeSet<SrvRecord> querySrvRecord(String host, String dnsserver) { + private static final TreeSet<SrvRecord> querySrvRecord(String host, InetAddress dnsServerAddress) { TreeSet<SrvRecord> result = new TreeSet<>(); + querySrvRecord(host, dnsServerAddress, false, result); + querySrvRecord(host, dnsServerAddress, true, result); + return result; + } + + private static final void querySrvRecord(String host, InetAddress dnsServerAddress, boolean tlsSrvRecord, TreeSet<SrvRecord> result) { + String qname = (tlsSrvRecord ? SECURE_CLIENT_SRV_PREFIX : CLIENT_SRV_PREFIX) + host; + String dnsServerHostAddress = dnsServerAddress.getHostAddress(); + Logging.d("dns", "using dns server: " + dnsServerHostAddress + " to look up " + qname); try { - InetAddress dnsServerAddress = InetAddress.getByName(dnsserver); - String qname = CLIENT_SRV_PREFIX + host; - DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN, dnsServerAddress.getHostAddress()); + DNSMessage message = client.query(qname, TYPE.SRV, CLASS.IN, dnsServerHostAddress); Record[] rrset = message.getAnswers(); for (Record rr : rrset) { Data d = rr.getPayload(); if (d instanceof SRV && NameUtil.idnEquals(qname, rr.getName())) { SRV srv = (SRV) d; - SrvRecord srvRecord = new SrvRecord(srv.getPriority(), srv.getName(), srv.getPort()); + SrvRecord srvRecord = new SrvRecord(srv.getPriority(), srv.getName(), srv.getPort(), tlsSrvRecord); result.add(srvRecord); } } } catch (IOException e) { - Logging.d("dns", "Error while retrieving SRV record for '" + host + "' from DNS '" + dnsserver + "': " + e.getMessage()); + Logging.d("dns", "Error while retrieving SRV record '" + qname + "' for '" + host + "' from DNS '" + dnsServerHostAddress + "': " + e.getMessage()); } - return result; } - /** - * Checks whether the given server is an IP address or not. - * The following patterns are treated as valid IP addresses: - * <ul> - * <li>{@link #PATTERN_IPV4}</li> - * <li>{@link #PATTERN_IPV6}</li> - * <li>{@link #PATTERN_IPV6_6HEX4DEC}</li> - * <li>{@link #PATTERN_IPV6_HEX4DECCOMPRESSED}</li> - * <li>{@link #PATTERN_IPV6_HEXCOMPRESSED}</li> - * </ul> - * @param server the string to check - * @return <code>true</code> if one of the patterns is matched <code>false</code> otherwise - */ public static boolean isIp(final String server) { - return 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(); + || PATTERN_IPV6_HEXCOMPRESSED.matcher(server).matches()); } } diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/ExceptionHandler.java b/src/main/java/de/thedevstack/conversationsplus/utils/ExceptionHandler.java index 256287f1..a810b089 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/ExceptionHandler.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/ExceptionHandler.java @@ -1,5 +1,7 @@ package de.thedevstack.conversationsplus.utils; +import android.content.Context; + import java.io.FileNotFoundException; import java.io.IOException; import java.io.OutputStream; @@ -8,8 +10,6 @@ import java.io.StringWriter; import java.io.Writer; import java.lang.Thread.UncaughtExceptionHandler; -import android.content.Context; - public class ExceptionHandler implements UncaughtExceptionHandler { private UncaughtExceptionHandler defaultHandler; diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/ExceptionHelper.java b/src/main/java/de/thedevstack/conversationsplus/utils/ExceptionHelper.java index 32f45127..913f2ab5 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/ExceptionHelper.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/ExceptionHelper.java @@ -1,5 +1,14 @@ package de.thedevstack.conversationsplus.utils; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.DialogInterface.OnClickListener; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.text.format.DateUtils; + import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; @@ -7,27 +16,17 @@ import java.io.InputStreamReader; import java.util.List; import de.thedevstack.android.logcat.Logging; -import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.ConversationsPlusPreferences; +import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.entities.Conversation; import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.services.XmppConnectionService; +import de.thedevstack.conversationsplus.ui.ConversationActivity; import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; import de.thedevstack.conversationsplus.xmpp.jid.Jid; -import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.SharedPreferences; -import android.content.DialogInterface.OnClickListener; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.content.pm.PackageManager.NameNotFoundException; -import android.preference.PreferenceManager; -import android.text.format.DateUtils; - public class ExceptionHelper { public static void init(Context context) { if (!(Thread.getDefaultUncaughtExceptionHandler() instanceof ExceptionHandler)) { @@ -36,12 +35,11 @@ public class ExceptionHelper { } } - public static void checkForCrash(final Context context, - final XmppConnectionService service) { + public static boolean checkForCrash(final ConversationActivity activity, final XmppConnectionService service) { try { boolean neverSend = ConversationsPlusPreferences.neverSend(); if (neverSend) { - return; + return false; } List<Account> accounts = service.getAccounts(); Account account = null; @@ -52,24 +50,25 @@ public class ExceptionHelper { } } if (account == null) { - return; + return false; } final Account finalAccount = account; - FileInputStream file = context.openFileInput("stacktrace.txt"); + FileInputStream file = activity.openFileInput("stacktrace.txt"); InputStreamReader inputStreamReader = new InputStreamReader(file); BufferedReader stacktrace = new BufferedReader(inputStreamReader); final StringBuilder report = new StringBuilder(); - PackageManager pm = context.getPackageManager(); + PackageManager pm = activity.getPackageManager(); PackageInfo packageInfo = null; try { - packageInfo = pm.getPackageInfo(context.getPackageName(), 0); + packageInfo = pm.getPackageInfo(activity.getPackageName(), 0); report.append("Version: " + packageInfo.versionName + '\n'); report.append("Last Update: " - + DateUtils.formatDateTime(context, - packageInfo.lastUpdateTime, - DateUtils.FORMAT_SHOW_TIME - | DateUtils.FORMAT_SHOW_DATE) + '\n'); + + DateUtils.formatDateTime(activity, + packageInfo.lastUpdateTime, + DateUtils.FORMAT_SHOW_TIME + | DateUtils.FORMAT_SHOW_DATE) + '\n'); } catch (NameNotFoundException e) { + return false; } String line; while ((line = stacktrace.readLine()) != null) { @@ -77,11 +76,11 @@ public class ExceptionHelper { report.append('\n'); } file.close(); - context.deleteFile("stacktrace.txt"); - AlertDialog.Builder builder = new AlertDialog.Builder(context); - builder.setTitle(context.getString(R.string.crash_report_title)); - builder.setMessage(context.getText(R.string.crash_report_message)); - builder.setPositiveButton(context.getText(R.string.send_now), + activity.deleteFile("stacktrace.txt"); + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle(activity.getString(R.string.crash_report_title)); + builder.setMessage(activity.getText(R.string.crash_report_message)); + builder.setPositiveButton(activity.getText(R.string.send_now), new OnClickListener() { @Override @@ -90,18 +89,18 @@ public class ExceptionHelper { Logging.d(Config.LOGTAG, "using account=" + finalAccount.getJid().toBareJid() + " to send in stack trace"); - Conversation conversation = null; - try { - conversation = service.findOrCreateConversation(finalAccount, - Jid.fromString(context.getString(R.string.cplus_bugreport_jabberid)), false); - } catch (final InvalidJidException ignored) { - } - Message message = new Message(conversation, report + Conversation conversation = null; + try { + conversation = service.findOrCreateConversation(finalAccount, + Jid.fromString(activity.getString(R.string.cplus_bugreport_jabberid)), false); + } catch (final InvalidJidException ignored) { + } + Message message = new Message(conversation, report .toString(), Message.ENCRYPTION_NONE); service.sendMessage(message); } }); - builder.setNegativeButton(context.getText(R.string.send_never), + builder.setNegativeButton(activity.getText(R.string.send_never), new OnClickListener() { @Override @@ -110,8 +109,9 @@ public class ExceptionHelper { } }); builder.create().show(); + return true; } catch (final IOException ignored) { - } - + return false; + } } } diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/ExifHelper.java b/src/main/java/de/thedevstack/conversationsplus/utils/ExifHelper.java index 576698ce..031bdfa5 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/ExifHelper.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/ExifHelper.java @@ -16,11 +16,11 @@ package de.thedevstack.conversationsplus.utils; -import de.thedevstack.android.logcat.Logging; - import java.io.IOException; import java.io.InputStream; +import de.thedevstack.android.logcat.Logging; + public class ExifHelper { private static final String TAG = "CameraExif"; diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/FileUtils.java b/src/main/java/de/thedevstack/conversationsplus/utils/FileUtils.java new file mode 100644 index 00000000..2cf5679b --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/utils/FileUtils.java @@ -0,0 +1,149 @@ +package de.thedevstack.conversationsplus.utils; + +import android.annotation.SuppressLint; +import android.content.ContentUris; +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; +import android.os.Build; +import android.os.Environment; +import android.provider.DocumentsContract; +import android.provider.MediaStore; + +public class FileUtils { + + /** + * Get a file path from a Uri. This will get the the path for Storage Access + * Framework Documents, as well as the _data field for the MediaStore and + * other file-based ContentProviders. + * + * @param context The context. + * @param uri The Uri to query. + * @author paulburke + */ + @SuppressLint("NewApi") + public static String getPath(final Context context, final Uri uri) { + if (uri == null) { + return null; + } + + final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; + + // DocumentProvider + if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { + // ExternalStorageProvider + if (isExternalStorageDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + if ("primary".equalsIgnoreCase(type)) { + return Environment.getExternalStorageDirectory() + "/" + split[1]; + } + + // TODO handle non-primary volumes + } + // DownloadsProvider + else if (isDownloadsDocument(uri)) { + + final String id = DocumentsContract.getDocumentId(uri); + final Uri contentUri = ContentUris.withAppendedId( + Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); + + return getDataColumn(context, contentUri, null, null); + } + // MediaProvider + else if (isMediaDocument(uri)) { + final String docId = DocumentsContract.getDocumentId(uri); + final String[] split = docId.split(":"); + final String type = split[0]; + + Uri contentUri = null; + if ("image".equals(type)) { + contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; + } else if ("video".equals(type)) { + contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; + } else if ("audio".equals(type)) { + contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; + } + + final String selection = "_id=?"; + final String[] selectionArgs = new String[]{ + split[1] + }; + + return getDataColumn(context, contentUri, selection, selectionArgs); + } + } + // MediaStore (and general) + else if ("content".equalsIgnoreCase(uri.getScheme())) { + return getDataColumn(context, uri, null, null); + } + // File + else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + + return null; + } + + /** + * Get the value of the data column for this Uri. This is useful for + * MediaStore Uris, and other file-based ContentProviders. + * + * @param context The context. + * @param uri The Uri to query. + * @param selection (Optional) Filter used in the query. + * @param selectionArgs (Optional) Selection arguments used in the query. + * @return The value of the _data column, which is typically a file path. + */ + public static String getDataColumn(Context context, Uri uri, String selection, + String[] selectionArgs) { + + Cursor cursor = null; + final String column = "_data"; + final String[] projection = { + column + }; + + try { + cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,null); + if (cursor != null && cursor.moveToFirst()) { + final int column_index = cursor.getColumnIndexOrThrow(column); + return cursor.getString(column_index); + } + } catch(Exception e) { + return null; + } finally { + if (cursor != null) { + cursor.close(); + } + } + return null; + } + + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is ExternalStorageProvider. + */ + public static boolean isExternalStorageDocument(Uri uri) { + return "com.android.externalstorage.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is DownloadsProvider. + */ + public static boolean isDownloadsDocument(Uri uri) { + return "com.android.providers.downloads.documents".equals(uri.getAuthority()); + } + + /** + * @param uri The Uri to check. + * @return Whether the Uri authority is MediaProvider. + */ + public static boolean isMediaDocument(Uri uri) { + return "com.android.providers.media.documents".equals(uri.getAuthority()); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java index 9250c432..bf79d7e0 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/ImageUtil.java @@ -11,16 +11,14 @@ import android.util.LruCache; import java.io.File; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.exceptions.ImageResizeException; import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.entities.Message; -import de.thedevstack.conversationsplus.exceptions.FileCopyException; -import de.thedevstack.conversationsplus.exceptions.ImageResizeException; import de.thedevstack.conversationsplus.persistance.FileBackend; /** diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java index c04ebdb8..7afc3fa4 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/MessageUtil.java @@ -3,6 +3,8 @@ package de.thedevstack.conversationsplus.utils; import android.graphics.BitmapFactory; import java.net.URL; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import de.thedevstack.conversationsplus.entities.DownloadableFile; import de.thedevstack.conversationsplus.entities.Message; @@ -12,6 +14,26 @@ import de.thedevstack.conversationsplus.persistance.FileBackend; * Created by tzur on 15.12.2015. */ public final class MessageUtil { + public static boolean wasHighlightedOrPrivate(final Message message) { + final String nick = message.getConversation().getMucOptions().getActualNick(); + final Pattern highlight = generateNickHighlightPattern(nick); + if (message.getBody() == null || nick == null) { + return false; + } + final Matcher m = highlight.matcher(message.getBody()); + return (m.find() || message.getType() == Message.TYPE_PRIVATE); + } + + private static Pattern generateNickHighlightPattern(final String nick) { + // We expect a word boundary, i.e. space or start of string, followed by + // the + // nick (matched in case-insensitive manner), followed by optional + // punctuation (for example "bob: i disagree" or "how are you alice?"), + // followed by another word boundary. + return Pattern.compile("\\b" + Pattern.quote(nick) + "\\p{Punct}?\\b", + Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE); + } + public static void updateMessageWithImageDetails(Message message, String filePath, long size, int imageWidth, int imageHeight) { message.setRelativeFilePath(filePath); MessageUtil.updateMessageBodyWithImageParams(message, size, imageWidth, imageHeight); diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/MimeUtils.java b/src/main/java/de/thedevstack/conversationsplus/utils/MimeUtils.java index c1c40f92..fdf55d2d 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/MimeUtils.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/MimeUtils.java @@ -458,7 +458,7 @@ public final class MimeUtils { if (extension == null || extension.isEmpty()) { return null; } - return extensionToMimeTypeMap.get(extension); + return extensionToMimeTypeMap.get(extension.toLowerCase()); } /** * Returns true if the given extension has a registered MIME type. diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/OnPhoneContactsLoadedListener.java b/src/main/java/de/thedevstack/conversationsplus/utils/OnPhoneContactsLoadedListener.java index e701d62b..eecee159 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/OnPhoneContactsLoadedListener.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/OnPhoneContactsLoadedListener.java @@ -1,9 +1,9 @@ package de.thedevstack.conversationsplus.utils; -import java.util.List; - import android.os.Bundle; +import java.util.List; + public interface OnPhoneContactsLoadedListener { public void onPhoneContactsLoaded(List<Bundle> phoneContacts); } diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/PhoneHelper.java b/src/main/java/de/thedevstack/conversationsplus/utils/PhoneHelper.java index d1f598c0..86a67661 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/PhoneHelper.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/PhoneHelper.java @@ -1,8 +1,6 @@ package de.thedevstack.conversationsplus.utils; -import java.util.List; -import java.util.concurrent.RejectedExecutionException; - +import android.Manifest; import android.content.Context; import android.content.CursorLoader; import android.content.Loader; @@ -10,20 +8,27 @@ import android.content.Loader.OnLoadCompleteListener; import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.provider.ContactsContract; import android.provider.ContactsContract.Profile; -import de.thedevstack.conversationsplus.ConversationsPlusApplication; +import java.util.List; +import java.util.concurrent.RejectedExecutionException; public class PhoneHelper { - public static void loadPhoneContacts(Context context,final List<Bundle> phoneContacts, final OnPhoneContactsLoadedListener listener) { - final String[] PROJECTION = new String[] { ContactsContract.Data._ID, + public static void loadPhoneContacts(Context context, final List<Bundle> phoneContacts, final OnPhoneContactsLoadedListener listener) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { + listener.onPhoneContactsLoaded(phoneContacts); + return; + } + final String[] PROJECTION = new String[]{ContactsContract.Data._ID, ContactsContract.Data.DISPLAY_NAME, ContactsContract.Data.PHOTO_URI, ContactsContract.Data.LOOKUP_KEY, - ContactsContract.CommonDataKinds.Im.DATA }; + ContactsContract.CommonDataKinds.Im.DATA}; final String SELECTION = "(" + ContactsContract.Data.MIMETYPE + "=\"" + ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE @@ -31,39 +36,39 @@ public class PhoneHelper { + "=\"" + ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER + "\")"; - CursorLoader mCursorLoader = new CursorLoader(context, + CursorLoader mCursorLoader = new NotThrowCursorLoader(context, ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, null, null); mCursorLoader.registerListener(0, new OnLoadCompleteListener<Cursor>() { @Override public void onLoadComplete(Loader<Cursor> arg0, Cursor cursor) { - if (cursor == null) { - return; - } - while (cursor.moveToNext()) { - Bundle contact = new Bundle(); - contact.putInt("phoneid", cursor.getInt(cursor - .getColumnIndex(ContactsContract.Data._ID))); - contact.putString( - "displayname", - cursor.getString(cursor - .getColumnIndex(ContactsContract.Data.DISPLAY_NAME))); - contact.putString("photouri", cursor.getString(cursor - .getColumnIndex(ContactsContract.Data.PHOTO_URI))); - contact.putString("lookup", cursor.getString(cursor - .getColumnIndex(ContactsContract.Data.LOOKUP_KEY))); - - contact.putString( - "jid", - cursor.getString(cursor - .getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA))); - phoneContacts.add(contact); + if (cursor != null) { + while (cursor.moveToNext()) { + Bundle contact = new Bundle(); + contact.putInt("phoneid", cursor.getInt(cursor + .getColumnIndex(ContactsContract.Data._ID))); + contact.putString( + "displayname", + cursor.getString(cursor + .getColumnIndex(ContactsContract.Data.DISPLAY_NAME))); + contact.putString("photouri", cursor.getString(cursor + .getColumnIndex(ContactsContract.Data.PHOTO_URI))); + contact.putString("lookup", cursor.getString(cursor + .getColumnIndex(ContactsContract.Data.LOOKUP_KEY))); + + contact.putString( + "jid", + cursor.getString(cursor + .getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA))); + phoneContacts.add(contact); + } + cursor.close(); } + if (listener != null) { listener.onPhoneContactsLoaded(phoneContacts); } - cursor.close(); } }); try { @@ -75,8 +80,30 @@ public class PhoneHelper { } } + private static class NotThrowCursorLoader extends CursorLoader { + + public NotThrowCursorLoader(Context c, Uri u, String[] p, String s, String[] sa, String so) { + super(c, u, p, s, sa, so); + } + + @Override + public Cursor loadInBackground() { + + try { + return (super.loadInBackground()); + } catch (SecurityException e) { + return(null); + } + } + + } + public static Uri getSefliUri(Context context) { - String[] mProjection = new String[] { Profile._ID, Profile.PHOTO_URI }; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M + && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { + return null; + } + String[] mProjection = new String[]{Profile._ID, Profile.PHOTO_URI}; Cursor mProfileCursor = context.getContentResolver().query( Profile.CONTENT_URI, mProjection, null, null, null); @@ -93,4 +120,17 @@ public class PhoneHelper { } } } + + public static String getVersionName(Context context) { + final String packageName = context == null ? null : context.getPackageName(); + if (packageName != null) { + try { + return context.getPackageManager().getPackageInfo(packageName, 0).versionName; + } catch (final PackageManager.NameNotFoundException | RuntimeException e) { + return "unknown"; + } + } else { + return "unknown"; + } + } } diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/SSLSocketHelper.java b/src/main/java/de/thedevstack/conversationsplus/utils/SSLSocketHelper.java new file mode 100644 index 00000000..1842e897 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/utils/SSLSocketHelper.java @@ -0,0 +1,73 @@ +package de.thedevstack.conversationsplus.utils; + +import android.os.Build; + +import java.lang.reflect.Method; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedList; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +public class SSLSocketHelper { + + public static void setSecurity(final SSLSocket sslSocket) throws NoSuchAlgorithmException { + final String[] supportProtocols; + final Collection<String> supportedProtocols = new LinkedList<>( + Arrays.asList(sslSocket.getSupportedProtocols())); + supportedProtocols.remove("SSLv3"); + supportProtocols = supportedProtocols.toArray(new String[supportedProtocols.size()]); + + sslSocket.setEnabledProtocols(supportProtocols); + + final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites( + sslSocket.getSupportedCipherSuites()); + if (cipherSuites.length > 0) { + sslSocket.setEnabledCipherSuites(cipherSuites); + } + } + + public static void setSNIHost(final SSLSocketFactory factory, final SSLSocket socket, final String hostname) { + if (factory instanceof android.net.SSLCertificateSocketFactory && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { + ((android.net.SSLCertificateSocketFactory) factory).setHostname(socket, hostname); + } else { + try { + socket.getClass().getMethod("setHostname", String.class).invoke(socket, hostname); + } catch (Throwable e) { + // ignore any error, we just can't set the hostname... + } + } + } + + public static void setAlpnProtocol(final SSLSocketFactory factory, final SSLSocket socket, final String protocol) { + try { + if (factory instanceof android.net.SSLCertificateSocketFactory && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { + // can't call directly because of @hide? + //((android.net.SSLCertificateSocketFactory)factory).setAlpnProtocols(new byte[][]{protocol.getBytes("UTF-8")}); + android.net.SSLCertificateSocketFactory.class.getMethod("setAlpnProtocols", byte[][].class).invoke(socket, new Object[]{new byte[][]{protocol.getBytes("UTF-8")}}); + } else { + final Method method = socket.getClass().getMethod("setAlpnProtocols", byte[].class); + // the concatenation of 8-bit, length prefixed protocol names, just one in our case... + // http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4 + final byte[] protocolUTF8Bytes = protocol.getBytes("UTF-8"); + final byte[] lengthPrefixedProtocols = new byte[protocolUTF8Bytes.length + 1]; + lengthPrefixedProtocols[0] = (byte) protocol.length(); // cannot be over 255 anyhow + System.arraycopy(protocolUTF8Bytes, 0, lengthPrefixedProtocols, 1, protocolUTF8Bytes.length); + method.invoke(socket, new Object[]{lengthPrefixedProtocols}); + } + } catch (Throwable e) { + // ignore any error, we just can't set the alpn protocol... + } + } + + public static SSLContext getSSLContext() throws NoSuchAlgorithmException { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + return SSLContext.getInstance("TLSv1.2"); + } else { + return SSLContext.getInstance("TLS"); + } + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/SerialSingleThreadExecutor.java b/src/main/java/de/thedevstack/conversationsplus/utils/SerialSingleThreadExecutor.java index 264645c6..4871af43 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/SerialSingleThreadExecutor.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/SerialSingleThreadExecutor.java @@ -31,4 +31,4 @@ public class SerialSingleThreadExecutor implements Executor { executor.execute(active); } } -} +}
\ No newline at end of file diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/SocksSocketFactory.java b/src/main/java/de/thedevstack/conversationsplus/utils/SocksSocketFactory.java new file mode 100644 index 00000000..940399c3 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/utils/SocksSocketFactory.java @@ -0,0 +1,52 @@ +package de.thedevstack.conversationsplus.utils; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.nio.ByteBuffer; + +import de.thedevstack.conversationsplus.Config; + +public class SocksSocketFactory { + + public static void createSocksConnection(Socket socket, String destination, int port) throws IOException { + InputStream proxyIs = socket.getInputStream(); + OutputStream proxyOs = socket.getOutputStream(); + proxyOs.write(new byte[]{0x05, 0x01, 0x00}); + byte[] response = new byte[2]; + proxyIs.read(response); + byte[] dest = destination.getBytes(); + ByteBuffer request = ByteBuffer.allocate(7 + dest.length); + request.put(new byte[]{0x05, 0x01, 0x00, 0x03}); + request.put((byte) dest.length); + request.put(dest); + request.putShort((short) port); + proxyOs.write(request.array()); + response = new byte[7 + dest.length]; + proxyIs.read(response); + if (response[1] != 0x00) { + throw new SocksConnectionException(); + } + } + + public static Socket createSocket(InetSocketAddress address, String destination, int port) throws IOException { + Socket socket = new Socket(); + try { + socket.connect(address, Config.CONNECT_TIMEOUT * 1000); + } catch (IOException e) { + throw new SocksProxyNotFoundException(); + } + createSocksConnection(socket, destination, port); + return socket; + } + + static class SocksConnectionException extends IOException { + + } + + public static class SocksProxyNotFoundException extends IOException { + + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/StreamUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/StreamUtil.java index 64f46314..729bdf11 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/StreamUtil.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/StreamUtil.java @@ -6,6 +6,7 @@ import java.io.Closeable; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.net.Socket; import de.thedevstack.conversationsplus.ConversationsPlusApplication; @@ -40,6 +41,20 @@ public final class StreamUtil { } /** + * Closes a socket. + * IOException is silently ignored. + * @param socket the socket to close + */ + public static void close(Socket socket) { + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + } + } + } + + /** * Avoid instantiation of util class. */ private StreamUtil() { diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/UIHelper.java b/src/main/java/de/thedevstack/conversationsplus/utils/UIHelper.java index 532ff1cb..e1915e99 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/UIHelper.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/UIHelper.java @@ -1,44 +1,27 @@ package de.thedevstack.conversationsplus.utils; +import android.content.Context; +import android.text.format.DateFormat; +import android.text.format.DateUtils; +import android.util.Pair; + import java.math.BigDecimal; -import java.text.NumberFormat; import java.util.ArrayList; import java.util.Arrays; -import java.net.URLConnection; import java.util.Calendar; import java.util.Date; -import java.util.HashMap; import java.util.Locale; -import java.util.Map; -import java.util.Map.Entry; -import java.util.regex.Pattern; -import java.util.regex.Matcher; -import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.entities.Contact; import de.thedevstack.conversationsplus.entities.Conversation; -import de.thedevstack.conversationsplus.entities.Presences; -import de.thedevstack.conversationsplus.entities.Transferable; import de.thedevstack.conversationsplus.entities.Message; +import de.thedevstack.conversationsplus.entities.Presence; +import de.thedevstack.conversationsplus.entities.Transferable; import de.thedevstack.conversationsplus.xmpp.jid.Jid; -import android.content.Context; -import android.text.format.DateFormat; -import android.text.format.DateUtils; -import android.text.Spannable.Factory; -import android.text.style.ImageSpan; -import android.text.Spannable; -import android.util.Pair; - public class UIHelper { - private static String BLACK_HEART_SUIT = "\u2665"; - private static String HEAVY_BLACK_HEART_SUIT = "\u2764"; - private static String WHITE_HEART_SUIT = "\u2661"; - - public static final ArrayList<String> HEARTS = new ArrayList<>(Arrays.asList(BLACK_HEART_SUIT,HEAVY_BLACK_HEART_SUIT,WHITE_HEART_SUIT)); - private static final ArrayList<String> LOCATION_QUESTIONS = new ArrayList<>(Arrays.asList( "where are you", //en "where are you now", //en @@ -144,7 +127,7 @@ public class UIHelper { } public static int getColorForName(String name) { - if (name.isEmpty()) { + if (name == null || name.isEmpty()) { return 0xFF202020; } int colors[] = {0xFFe91e63, 0xFF9c27b0, 0xFF673ab7, 0xFF3f51b5, @@ -184,7 +167,9 @@ public class UIHelper { return new Pair<>("",false); } } else if (message.getEncryption() == Message.ENCRYPTION_PGP) { - return new Pair<>(context.getString(R.string.encrypted_message_received),true); + return new Pair<>(context.getString(R.string.pgp_message),true); + } else if (message.getEncryption() == Message.ENCRYPTION_DECRYPTION_FAILED) { + return new Pair<>(context.getString(R.string.decryption_failed), true); } else if (message.getType() == Message.TYPE_FILE || message.getType() == Message.TYPE_IMAGE) { if (message.getStatus() == Message.STATUS_RECEIVED) { return new Pair<>(context.getString(R.string.received_x_file, @@ -254,15 +239,15 @@ public class UIHelper { } } - public static String getStatusColor(int status) { + public static String getStatusColor(Presence.Status status) { switch (status) { - case Presences.ONLINE: - case Presences.CHAT: + case ONLINE: + case CHAT: return "#259B23"; - case Presences.AWAY: - case Presences.XA: + case AWAY: + case XA: return "#FF9800"; - case Presences.DND: + case DND: return "#E51C23"; } return "#CCCCCC"; diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/Xmlns.java b/src/main/java/de/thedevstack/conversationsplus/utils/Xmlns.java index 85f99c30..f937e35f 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/Xmlns.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/Xmlns.java @@ -1,9 +1,11 @@ package de.thedevstack.conversationsplus.utils; +import de.thedevstack.conversationsplus.Config; + public final class Xmlns { public static final String BLOCKING = "urn:xmpp:blocking"; public static final String ROSTER = "jabber:iq:roster"; public static final String REGISTER = "jabber:iq:register"; public static final String BYTE_STREAMS = "http://jabber.org/protocol/bytestreams"; - public static final String HTTP_UPLOAD = "urn:xmpp:http:upload"; + public static final String HTTP_UPLOAD = Config.LEGACY_NAMESPACE_HTTP_UPLOAD ? "eu:siacs:conversations:http:upload" : "urn:xmpp:http:upload"; } diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/XmppSendUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/XmppSendUtil.java index eb3c5da4..11d05832 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/XmppSendUtil.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/XmppSendUtil.java @@ -4,6 +4,7 @@ import de.thedevstack.conversationsplus.entities.Account; import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; import de.thedevstack.conversationsplus.xmpp.XmppConnection; import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; +import de.thedevstack.conversationsplus.xmpp.stanzas.MessagePacket; import de.thedevstack.conversationsplus.xmpp.stanzas.PresencePacket; /** @@ -23,4 +24,11 @@ public class XmppSendUtil { connection.sendPresencePacket(packet); } } + + public static void sendMessagePacket(Account account, MessagePacket packet) { + XmppConnection connection = account.getXmppConnection(); + if (connection != null) { + connection.sendMessagePacket(packet); + } + } } |