diff options
author | Christian Schneppe <christian@pix-art.de> | 2018-03-20 21:33:54 +0100 |
---|---|---|
committer | Christian Schneppe <christian@pix-art.de> | 2018-03-20 21:33:54 +0100 |
commit | 76e10f17a62f867116c146d7be9da3ea8dfc311d (patch) | |
tree | 63ca6a8902078517e5dd8447a78decb65f3fa6f0 /src/main/java/de/pixart | |
parent | 7df82563ab8650e3694a2b64acc9864e443078f3 (diff) | |
parent | 17d279b9c3f7dfa716592cca9f5fb2c9f3dd3cf6 (diff) |
Merge branch 'app_compat'
Diffstat (limited to 'src/main/java/de/pixart')
49 files changed, 2244 insertions, 1334 deletions
diff --git a/src/main/java/de/pixart/messenger/Config.java b/src/main/java/de/pixart/messenger/Config.java index 8783e1c64..60f2410aa 100644 --- a/src/main/java/de/pixart/messenger/Config.java +++ b/src/main/java/de/pixart/messenger/Config.java @@ -114,6 +114,8 @@ public final class Config { public static final boolean X509_VERIFICATION = false; //use x509 certificates to verify OMEMO keys + public static final boolean ONLY_INTERNAL_STORAGE = false; //use internal storage instead of sdcard to save attachments + public static final boolean IGNORE_ID_REWRITE_IN_MUC = true; public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY * 5; diff --git a/src/main/java/de/pixart/messenger/crypto/DomainHostnameVerifier.java b/src/main/java/de/pixart/messenger/crypto/DomainHostnameVerifier.java new file mode 100644 index 000000000..0ceb650f3 --- /dev/null +++ b/src/main/java/de/pixart/messenger/crypto/DomainHostnameVerifier.java @@ -0,0 +1,10 @@ +package de.pixart.messenger.crypto; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSession; + +public interface DomainHostnameVerifier extends HostnameVerifier { + + boolean verify(String domain, String hostname, SSLSession sslSession); + +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/crypto/XmppDomainVerifier.java b/src/main/java/de/pixart/messenger/crypto/XmppDomainVerifier.java index e811a0f39..3f3c358a7 100644 --- a/src/main/java/de/pixart/messenger/crypto/XmppDomainVerifier.java +++ b/src/main/java/de/pixart/messenger/crypto/XmppDomainVerifier.java @@ -25,8 +25,6 @@ import java.util.List; import javax.net.ssl.SSLSession; -import de.duenndns.ssl.DomainHostnameVerifier; - public class XmppDomainVerifier implements DomainHostnameVerifier { private static final String LOGTAG = "XmppDomainVerifier"; diff --git a/src/main/java/de/pixart/messenger/entities/MTMDecision.java b/src/main/java/de/pixart/messenger/entities/MTMDecision.java new file mode 100644 index 000000000..38cc4b578 --- /dev/null +++ b/src/main/java/de/pixart/messenger/entities/MTMDecision.java @@ -0,0 +1,33 @@ +/* MemorizingTrustManager - a TrustManager which asks the user about invalid + * certificates and memorizes their decision. + * + * Copyright (c) 2010 Georg Lukas <georg@op-co.de> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.pixart.messenger.entities; + +public class MTMDecision { + public final static int DECISION_INVALID = 0; + public final static int DECISION_ABORT = 1; + public final static int DECISION_ONCE = 2; + public final static int DECISION_ALWAYS = 3; + + public int state = DECISION_INVALID; +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/generator/PresenceGenerator.java b/src/main/java/de/pixart/messenger/generator/PresenceGenerator.java index a6e46cb76..9bbf2191d 100644 --- a/src/main/java/de/pixart/messenger/generator/PresenceGenerator.java +++ b/src/main/java/de/pixart/messenger/generator/PresenceGenerator.java @@ -57,7 +57,7 @@ public class PresenceGenerator extends AbstractGenerator { Element cap = packet.addChild("c", "http://jabber.org/protocol/caps"); cap.setAttribute("hash", "sha-1"); - cap.setAttribute("node", "http://conversations.im"); + cap.setAttribute("node", "http://jabber.pix-art.de"); cap.setAttribute("ver", capHash); } return packet; diff --git a/src/main/java/de/pixart/messenger/services/MemorizingTrustManager.java b/src/main/java/de/pixart/messenger/services/MemorizingTrustManager.java new file mode 100644 index 000000000..d98e97a58 --- /dev/null +++ b/src/main/java/de/pixart/messenger/services/MemorizingTrustManager.java @@ -0,0 +1,904 @@ +/* MemorizingTrustManager - a TrustManager which asks the user about invalid + * certificates and memorizes their decision. + * + * Copyright (c) 2010 Georg Lukas <georg@op-co.de> + * + * MemorizingTrustManager.java contains the actual trust manager and interface + * code to create a MemorizingActivity and obtain the results. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.pixart.messenger.services; + +import android.app.Application; +import android.app.NotificationManager; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Handler; +import android.preference.PreferenceManager; +import android.support.v7.app.AppCompatActivity; +import android.util.Base64; +import android.util.Log; +import android.util.SparseArray; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.URL; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.Certificate; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateParsingException; +import java.security.cert.X509Certificate; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.List; +import java.util.Locale; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.regex.Pattern; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.TrustManagerFactory; +import javax.net.ssl.X509TrustManager; + +import de.pixart.messenger.R; +import de.pixart.messenger.crypto.DomainHostnameVerifier; +import de.pixart.messenger.entities.MTMDecision; +import de.pixart.messenger.ui.MemorizingActivity; + +/** + * A X509 trust manager implementation which asks the user about invalid + * certificates and memorizes their decision. + * <p> + * The certificate validity is checked using the system default X509 + * TrustManager, creating a query Dialog if the check fails. + * <p> + * <b>WARNING:</b> This only works if a dedicated thread is used for + * opening sockets! + */ +public class MemorizingTrustManager { + + + 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"); + + 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"; + final static String DECISION_INTENT_CHOICE = DECISION_INTENT + ".decisionChoice"; + + private final static Logger LOGGER = Logger.getLogger(MemorizingTrustManager.class.getName()); + public final static String DECISION_TITLE_ID = DECISION_INTENT + ".titleId"; + private final static int NOTIFICATION_ID = 100509; + + final static String NO_TRUST_ANCHOR = "Trust anchor for certification path not found."; + + static String KEYSTORE_DIR = "KeyStore"; + static String KEYSTORE_FILE = "KeyStore.bks"; + + Context master; + AppCompatActivity foregroundAct; + NotificationManager notificationManager; + private static int decisionId = 0; + private static SparseArray<MTMDecision> openDecisions = new SparseArray<MTMDecision>(); + + Handler masterHandler; + private File keyStoreFile; + private KeyStore appKeyStore; + private X509TrustManager defaultTrustManager; + private X509TrustManager appTrustManager; + 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. + * + * @param m 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); + 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. + */ + public MemorizingTrustManager(Context m) { + init(m); + this.appTrustManager = getTrustManager(appKeyStore); + this.defaultTrustManager = getTrustManager(null); + } + + void init(Context m) { + master = m; + masterHandler = new Handler(m.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(); + } else 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); + + poshCacheDir = app.getFilesDir().getAbsolutePath()+"/posh_cache/"; + + appKeyStore = loadAppKeyStore(); + } + + + /** + * Binds an Activity to the MTM for displaying the query dialog. + * + * This is useful if your connection is run from a service that is + * triggered by user interaction -- in such cases the activity is + * visible and the user tends to ignore the service notification. + * + * You should never have a hidden activity bound to MTM! Use this + * function in onResume() and @see unbindDisplayActivity in onPause(). + * + * @param act Activity to be bound + */ + public void bindDisplayActivity(AppCompatActivity act) { + foregroundAct = act; + } + + /** + * Removes an Activity from the MTM display stack. + * + * Always call this function when the Activity added with + * {@link #bindDisplayActivity(AppCompatActivity)} is hidden. + * + * @param act Activity to be unbound + */ + public void unbindDisplayActivity(AppCompatActivity act) { + // do not remove if it was overridden by a different activity + if (foregroundAct == act) + foregroundAct = null; + } + + /** + * Changes the path for the KeyStore file. + * + * The actual filename relative to the app's directory will be + * <code>app_<i>dirname</i>/<i>filename</i></code>. + * + * @param dirname directory to store the KeyStore. + * @param filename file name for the KeyStore. + */ + public static void setKeyStoreFile(String dirname, String filename) { + KEYSTORE_DIR = dirname; + KEYSTORE_FILE = filename; + } + + /** + * Get a list of all certificate aliases stored in MTM. + * + * @return an {@link Enumeration} of all certificates + */ + public Enumeration<String> getCertificates() { + try { + return appKeyStore.aliases(); + } catch (KeyStoreException e) { + // this should never happen, however... + throw new RuntimeException(e); + } + } + + /** + * Get a certificate for a given alias. + * + * @param alias the certificate's alias as returned by {@link #getCertificates()}. + * + * @return the certificate associated with the alias or <tt>null</tt> if none found. + */ + public Certificate getCertificate(String alias) { + try { + return appKeyStore.getCertificate(alias); + } catch (KeyStoreException e) { + // this should never happen, however... + throw new RuntimeException(e); + } + } + + /** + * Removes the given certificate from MTMs key store. + * + * <p> + * <b>WARNING</b>: 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. + * </p> + * @param alias the certificate's alias as returned by {@link #getCertificates()}. + * + * @throws KeyStoreException if the certificate could not be deleted. + */ + public void deleteCertificate(String alias) throws KeyStoreException { + appKeyStore.deleteEntry(alias); + keyStoreUpdated(); + } + + /** + * Creates a new hostname verifier supporting user interaction. + * + * <p>This method creates a new {@link HostnameVerifier} that is bound to + * the given instance of {@link MemorizingTrustManager}, and leverages an + * existing {@link HostnameVerifier}. The returned verifier performs the + * following steps, returning as soon as one of them succeeds: + * </p> + * <ol> + * <li>Success, if the wrapped defaultVerifier accepts the certificate.</li> + * <li>Success, if the server certificate is stored in the keystore under the given hostname.</li> + * <li>Ask the user and return accordingly.</li> + * <li>Failure on exception.</li> + * </ol> + * + * @param defaultVerifier the {@link HostnameVerifier} that should perform the actual check + * @return a new hostname verifier using the MTM's key store + * + * @throws IllegalArgumentException if the defaultVerifier parameter is null + */ + public DomainHostnameVerifier wrapHostnameVerifier(final HostnameVerifier defaultVerifier, final boolean interactive) { + if (defaultVerifier == null) + throw new IllegalArgumentException("The default verifier may not be null"); + + return new MemorizingHostnameVerifier(defaultVerifier, interactive); + } + + X509TrustManager getTrustManager(KeyStore ks) { + try { + TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509"); + tmf.init(ks); + for (TrustManager t : tmf.getTrustManagers()) { + if (t instanceof X509TrustManager) { + return (X509TrustManager)t; + } + } + } catch (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); + } + return null; + } + + KeyStore loadAppKeyStore() { + KeyStore ks; + try { + ks = KeyStore.getInstance(KeyStore.getDefaultType()); + } catch (KeyStoreException e) { + LOGGER.log(Level.SEVERE, "getAppKeyStore()", e); + return null; + } + try { + ks.load(null, null); + ks.load(new java.io.FileInputStream(keyStoreFile), "MTM".toCharArray()); + } catch (java.io.FileNotFoundException e) { + LOGGER.log(Level.INFO, "getAppKeyStore(" + keyStoreFile + ") - file does not exist"); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "getAppKeyStore(" + keyStoreFile + ")", e); + } + return ks; + } + + void storeCert(String alias, Certificate cert) { + try { + appKeyStore.setCertificateEntry(alias, cert); + } catch (KeyStoreException e) { + LOGGER.log(Level.SEVERE, "storeCert(" + cert + ")", e); + return; + } + keyStoreUpdated(); + } + + void storeCert(X509Certificate cert) { + storeCert(cert.getSubjectDN().toString(), cert); + } + + void keyStoreUpdated() { + // reload appTrustManager + appTrustManager = getTrustManager(appKeyStore); + + // store KeyStore to file + java.io.FileOutputStream fos = null; + try { + fos = new java.io.FileOutputStream(keyStoreFile); + appKeyStore.store(fos, "MTM".toCharArray()); + } catch (Exception e) { + LOGGER.log(Level.SEVERE, "storeCert(" + keyStoreFile + ")", e); + } finally { + if (fos != null) { + try { + fos.close(); + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "storeCert(" + keyStoreFile + ")", e); + } + } + } + } + + // if the certificate is stored in the app key store, it is considered "known" + private boolean isCertKnown(X509Certificate cert) { + try { + return appKeyStore.getCertificateAlias(cert) != null; + } catch (KeyStoreException e) { + return false; + } + } + + private boolean isExpiredException(Throwable e) { + do { + if (e instanceof CertificateExpiredException) + return true; + e = e.getCause(); + } while (e != null); + return false; + } + + public void checkCertTrusted(X509Certificate[] chain, String authType, String domain, boolean isServer, boolean interactive) + throws CertificateException + { + 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); + } catch (CertificateException ae) { + LOGGER.log(Level.FINER, "checkCertTrusted: appTrustManager failed", ae); + // if the cert is stored in our appTrustManager, we ignore expiredness + if (isExpiredException(ae)) { + LOGGER.log(Level.INFO, "checkCertTrusted: accepting expired certificate from keystore"); + return; + } + if (isCertKnown(chain[0])) { + LOGGER.log(Level.INFO, "checkCertTrusted: accepting cert already stored in keystore"); + return; + } + try { + if (defaultTrustManager == null) + throw ae; + LOGGER.log(Level.FINE, "checkCertTrusted: trying defaultTrustManager"); + if (isServer) + defaultTrustManager.checkServerTrusted(chain, authType); + else + defaultTrustManager.checkClientTrusted(chain, authType); + } catch (CertificateException e) { + boolean trustSystemCAs = !PreferenceManager.getDefaultSharedPreferences(master).getBoolean("dont_trust_system_cas", false); + if (domain != null && isServer && trustSystemCAs && !isIp(domain)) { + String hash = getBase64Hash(chain[0],"SHA-256"); + List<String> fingerprints = getPoshFingerprints(domain); + if (hash != null && fingerprints.contains(hash)) { + Log.d("mtm","trusted cert fingerprint of "+domain+" via posh"); + return; + } + } + e.printStackTrace(); + if (interactive) { + interactCert(chain, authType, e); + } else { + throw e; + } + } + } + } + + private List<String> getPoshFingerprints(String domain) { + List<String> cached = getPoshFingerprintsFromCache(domain); + if (cached == null) { + return getPoshFingerprintsFromServer(domain); + } else { + return cached; + } + } + + private List<String> getPoshFingerprintsFromServer(String domain) { + return getPoshFingerprintsFromServer(domain, "https://"+domain+"/.well-known/posh/xmpp-client.json",-1,true); + } + + private List<String> getPoshFingerprintsFromServer(String domain, String url, int maxTtl, boolean followUrl) { + Log.d("mtm","downloading json for "+domain+" from "+url); + try { + List<String> results = new ArrayList<>(); + HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection(); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream())); + String inputLine; + StringBuilder builder = new StringBuilder(); + while ((inputLine = in.readLine()) != null) { + builder.append(inputLine); + } + JSONObject jsonObject = new JSONObject(builder.toString()); + in.close(); + int expires = jsonObject.getInt("expires"); + if (expires <= 0) { + return new ArrayList<>(); + } + if (maxTtl >= 0) { + expires = Math.min(maxTtl,expires); + } + String redirect; + try { + redirect = jsonObject.getString("url"); + } catch (JSONException e) { + redirect = null; + } + if (followUrl && redirect != null && redirect.toLowerCase().startsWith("https")) { + return getPoshFingerprintsFromServer(domain, redirect, expires, false); + } + JSONArray fingerprints = jsonObject.getJSONArray("fingerprints"); + for(int i = 0; i < fingerprints.length(); i++) { + JSONObject fingerprint = fingerprints.getJSONObject(i); + String sha256 = fingerprint.getString("sha-256"); + if (sha256 != null) { + results.add(sha256); + } + } + writeFingerprintsToCache(domain, results,1000L * expires+System.currentTimeMillis()); + return results; + } catch (Exception e) { + Log.d("mtm","error fetching posh "+e.getMessage()); + return new ArrayList<>(); + } + } + + private File getPoshCacheFile(String domain) { + return new File(poshCacheDir+domain+".json"); + } + + private void writeFingerprintsToCache(String domain, List<String> results, long expires) { + File file = getPoshCacheFile(domain); + file.getParentFile().mkdirs(); + try { + file.createNewFile(); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("expires",expires); + jsonObject.put("fingerprints",new JSONArray(results)); + FileOutputStream outputStream = new FileOutputStream(file); + outputStream.write(jsonObject.toString().getBytes()); + outputStream.flush(); + outputStream.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + private List<String> getPoshFingerprintsFromCache(String domain) { + File file = getPoshCacheFile(domain); + try { + InputStream is = new FileInputStream(file); + BufferedReader buf = new BufferedReader(new InputStreamReader(is)); + + String line = buf.readLine(); + StringBuilder sb = new StringBuilder(); + + while(line != null){ + sb.append(line).append("\n"); + line = buf.readLine(); + } + JSONObject jsonObject = new JSONObject(sb.toString()); + is.close(); + long expires = jsonObject.getLong("expires"); + long expiresIn = expires - System.currentTimeMillis(); + if (expiresIn < 0) { + file.delete(); + return null; + } else { + Log.d("mtm","posh fingerprints expire in "+(expiresIn/1000)+"s"); + } + List<String> result = new ArrayList<>(); + JSONArray jsonArray = jsonObject.getJSONArray("fingerprints"); + for(int i = 0; i < jsonArray.length(); ++i) { + result.add(jsonArray.getString(i)); + } + return result; + } catch (FileNotFoundException e) { + return null; + } catch (IOException e) { + return null; + } catch (JSONException e) { + file.delete(); + return null; + } + } + + private static boolean isIp(final String server) { + 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 { + MessageDigest md; + try { + md = MessageDigest.getInstance(digest); + } catch (NoSuchAlgorithmException e) { + return null; + } + md.update(certificate.getEncoded()); + return Base64.encodeToString(md.digest(),Base64.NO_WRAP); + } + + private X509Certificate[] getAcceptedIssuers() { + LOGGER.log(Level.FINE, "getAcceptedIssuers()"); + return defaultTrustManager.getAcceptedIssuers(); + } + + private int createDecisionId(MTMDecision d) { + int myId; + synchronized(openDecisions) { + myId = decisionId; + openDecisions.put(myId, d); + decisionId += 1; + } + return myId; + } + + private static String hexString(byte[] data) { + 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(":"); + } + return si.toString(); + } + + private static String certHash(final X509Certificate cert, String digest) { + try { + MessageDigest md = MessageDigest.getInstance(digest); + md.update(cert.getEncoded()); + return hexString(md.digest()); + } catch (java.security.cert.CertificateEncodingException e) { + return e.getMessage(); + } catch (java.security.NoSuchAlgorithmException e) { + return e.getMessage(); + } + } + + private void certDetails(StringBuffer si, X509Certificate c) { + SimpleDateFormat validityDateFormater = new SimpleDateFormat("yyyy-MM-dd"); + si.append("\n"); + si.append(c.getSubjectDN().toString()); + si.append("\n"); + si.append(validityDateFormater.format(c.getNotBefore())); + si.append(" - "); + si.append(validityDateFormater.format(c.getNotAfter())); + si.append("\nSHA-256: "); + si.append(certHash(c, "SHA-256")); + si.append("\nSHA-1: "); + si.append(certHash(c, "SHA-1")); + si.append("\nSigned by: "); + si.append(c.getIssuerDN().toString()); + si.append("\n"); + } + + private String certChainMessage(final X509Certificate[] chain, CertificateException cause) { + Throwable e = cause; + LOGGER.log(Level.FINE, "certChainMessage for " + e); + StringBuffer si = new StringBuffer(); + if (e.getCause() != null) { + e = e.getCause(); + // HACK: there is no sane way to check if the error is a "trust anchor + // 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()); + si.append("\n"); + } + si.append("\n"); + si.append(master.getString(R.string.mtm_connect_anyway)); + si.append("\n\n"); + si.append(master.getString(R.string.mtm_cert_details)); + for (X509Certificate c : chain) { + certDetails(si, c); + } + return si.toString(); + } + + private String hostNameMessage(X509Certificate cert, String hostname) { + StringBuffer si = new StringBuffer(); + + si.append(master.getString(R.string.mtm_hostname_mismatch, hostname)); + si.append("\n\n"); + try { + Collection<List<?>> sans = cert.getSubjectAlternativeNames(); + if (sans == null) { + si.append(cert.getSubjectDN()); + si.append("\n"); + } else for (List<?> altName : sans) { + Object name = altName.get(1); + if (name instanceof String) { + si.append("["); + si.append((Integer)altName.get(0)); + si.append("] "); + si.append(name); + si.append("\n"); + } + } + } catch (CertificateParsingException e) { + e.printStackTrace(); + si.append("<Parsing error: "); + si.append(e.getLocalizedMessage()); + si.append(">\n"); + } + si.append("\n"); + si.append(master.getString(R.string.mtm_connect_anyway)); + si.append("\n\n"); + si.append(master.getString(R.string.mtm_cert_details)); + certDetails(si, cert); + return si.toString(); + } + /** + * Returns the top-most entry of the activity stack. + * + * @return the Context of the currently bound UI or the master context if none is bound + */ + Context getUI() { + return (foregroundAct != null) ? foregroundAct : master; + } + + int interact(final String message, final int titleId) { + /* prepare the MTMDecision blocker object */ + 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); + + // 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 { + synchronized(choice) { choice.wait(); } + } catch (InterruptedException e) { + LOGGER.log(Level.FINER, "InterruptedException", e); + } + LOGGER.log(Level.FINE, "finished wait on " + myId + ": " + choice.state); + return choice.state; + } + + void interactCert(final X509Certificate[] chain, String authType, CertificateException cause) + throws CertificateException + { + switch (interact(certChainMessage(chain, cause), R.string.mtm_accept_cert)) { + case MTMDecision.DECISION_ALWAYS: + storeCert(chain[0]); // only store the server cert, not the whole chain + case MTMDecision.DECISION_ONCE: + break; + default: + throw (cause); + } + } + + boolean interactHostname(X509Certificate cert, String hostname) + { + switch (interact(hostNameMessage(cert, hostname), R.string.mtm_accept_servername)) { + case MTMDecision.DECISION_ALWAYS: + storeCert(hostname, cert); + case MTMDecision.DECISION_ONCE: + return true; + default: + return false; + } + } + + public static void interactResult(int decisionId, int choice) { + MTMDecision d; + synchronized(openDecisions) { + d = openDecisions.get(decisionId); + openDecisions.remove(decisionId); + } + if (d == null) { + LOGGER.log(Level.SEVERE, "interactResult: aborting due to stale decision reference!"); + return; + } + synchronized(d) { + d.state = choice; + d.notify(); + } + } + + class MemorizingHostnameVerifier implements DomainHostnameVerifier { + private final HostnameVerifier defaultVerifier; + private final boolean interactive; + + public MemorizingHostnameVerifier(HostnameVerifier wrapped, boolean interactive) { + this.defaultVerifier = wrapped; + this.interactive = interactive; + } + + @Override + public boolean verify(String domain, String hostname, SSLSession session) { + LOGGER.log(Level.FINE, "hostname verifier for " + domain + ", trying default verifier first"); + // if the default verifier accepts the hostname, we are done + if (defaultVerifier instanceof DomainHostnameVerifier) { + if (((DomainHostnameVerifier) defaultVerifier).verify(domain,hostname, session)) { + return true; + } + } else { + if (defaultVerifier.verify(domain, session)) { + return true; + } + } + + + // otherwise, we check if the hostname is an alias for this cert in our keystore + try { + X509Certificate cert = (X509Certificate)session.getPeerCertificates()[0]; + //Log.d(TAG, "cert: " + cert); + if (cert.equals(appKeyStore.getCertificate(domain.toLowerCase(Locale.US)))) { + LOGGER.log(Level.FINE, "certificate for " + domain + " is in our keystore. accepting."); + return true; + } else { + LOGGER.log(Level.FINE, "server " + domain + " provided wrong certificate, asking user."); + if (interactive) { + return interactHostname(cert, domain); + } else { + return false; + } + } + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + @Override + public boolean verify(String domain, SSLSession sslSession) { + return verify(domain,null,sslSession); + } + } + + + public X509TrustManager getNonInteractive(String domain) { + return new NonInteractiveMemorizingTrustManager(domain); + } + + public X509TrustManager getInteractive(String domain) { + return new InteractiveMemorizingTrustManager(domain); + } + + public X509TrustManager getNonInteractive() { + return new NonInteractiveMemorizingTrustManager(null); + } + + public X509TrustManager getInteractive() { + return new InteractiveMemorizingTrustManager(null); + } + + private class NonInteractiveMemorizingTrustManager implements X509TrustManager { + + private final String domain; + + public NonInteractiveMemorizingTrustManager(String domain) { + this.domain = domain; + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, false, false); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, true, false); + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return MemorizingTrustManager.this.getAcceptedIssuers(); + } + + } + + private class InteractiveMemorizingTrustManager implements X509TrustManager { + private final String domain; + + public InteractiveMemorizingTrustManager(String domain) { + this.domain = domain; + } + + @Override + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, false, true); + } + + @Override + public void checkServerTrusted(X509Certificate[] chain, String authType) + throws CertificateException { + MemorizingTrustManager.this.checkCertTrusted(chain, authType, domain, true, true); + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return MemorizingTrustManager.this.getAcceptedIssuers(); + } + } +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java index d26114b47..2c2f0b2d4 100644 --- a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java +++ b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java @@ -70,7 +70,6 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; -import de.duenndns.ssl.MemorizingTrustManager; import de.pixart.messenger.BuildConfig; import de.pixart.messenger.Config; import de.pixart.messenger.R; diff --git a/src/main/java/de/pixart/messenger/ui/AboutActivity.java b/src/main/java/de/pixart/messenger/ui/AboutActivity.java index b3b296c42..e00707d96 100644 --- a/src/main/java/de/pixart/messenger/ui/AboutActivity.java +++ b/src/main/java/de/pixart/messenger/ui/AboutActivity.java @@ -1,11 +1,11 @@ package de.pixart.messenger.ui; -import android.app.Activity; import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; import de.pixart.messenger.R; -public class AboutActivity extends Activity { +public class AboutActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { diff --git a/src/main/java/de/pixart/messenger/ui/AbstractSearchableListItemActivity.java b/src/main/java/de/pixart/messenger/ui/AbstractSearchableListItemActivity.java index 4852c5961..a3536a368 100644 --- a/src/main/java/de/pixart/messenger/ui/AbstractSearchableListItemActivity.java +++ b/src/main/java/de/pixart/messenger/ui/AbstractSearchableListItemActivity.java @@ -104,8 +104,7 @@ public abstract class AbstractSearchableListItemActivity extends XmppActivity { getMenuInflater().inflate(R.menu.choose_contact, menu); final MenuItem menuSearchView = menu.findItem(R.id.action_search); final View mSearchView = menuSearchView.getActionView(); - mSearchEditText = mSearchView - .findViewById(R.id.search_field); + mSearchEditText = mSearchView.findViewById(R.id.search_field); mSearchEditText.addTextChangedListener(mSearchTextWatcher); menuSearchView.setOnActionExpandListener(mOnActionExpandListener); return true; diff --git a/src/main/java/de/pixart/messenger/ui/BlockContactDialog.java b/src/main/java/de/pixart/messenger/ui/BlockContactDialog.java index 5a911298f..0775d2cf7 100644 --- a/src/main/java/de/pixart/messenger/ui/BlockContactDialog.java +++ b/src/main/java/de/pixart/messenger/ui/BlockContactDialog.java @@ -1,6 +1,6 @@ package de.pixart.messenger.ui; -import android.app.AlertDialog; +import android.support.v7.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.text.SpannableString; diff --git a/src/main/java/de/pixart/messenger/ui/ChangePasswordActivity.java b/src/main/java/de/pixart/messenger/ui/ChangePasswordActivity.java index 49a826f29..f6bec9838 100644 --- a/src/main/java/de/pixart/messenger/ui/ChangePasswordActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ChangePasswordActivity.java @@ -2,15 +2,16 @@ package de.pixart.messenger.ui; import android.content.Intent; import android.os.Bundle; +import android.support.design.widget.TextInputLayout; import android.view.View; import android.widget.Button; import android.widget.EditText; -import android.widget.TextView; import android.widget.Toast; import de.pixart.messenger.R; import de.pixart.messenger.entities.Account; import de.pixart.messenger.services.XmppConnectionService; +import de.pixart.messenger.ui.widget.DisabledActionModeCallback; public class ChangePasswordActivity extends XmppActivity implements XmppConnectionService.OnAccountPasswordChanged { @@ -21,20 +22,17 @@ public class ChangePasswordActivity extends XmppActivity implements XmppConnecti if (mAccount != null) { final String currentPassword = mCurrentPassword.getText().toString(); final String newPassword = mNewPassword.getText().toString(); - final String newPasswordConfirm = mNewPasswordConfirm.getText().toString(); if (!mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE) && !currentPassword.equals(mAccount.getPassword())) { mCurrentPassword.requestFocus(); - mCurrentPassword.setError(getString(R.string.account_status_unauthorized)); - } else if (!newPassword.equals(newPasswordConfirm)) { - mNewPasswordConfirm.requestFocus(); - mNewPasswordConfirm.setError(getString(R.string.passwords_do_not_match)); + mCurrentPasswordLayout.setError(getString(R.string.account_status_unauthorized)); + removeErrorsOnAllBut(mCurrentPasswordLayout); } else if (newPassword.trim().isEmpty()) { mNewPassword.requestFocus(); - mNewPassword.setError(getString(R.string.password_should_not_be_empty)); + mNewPasswordLayout.setError(getString(R.string.password_should_not_be_empty)); + removeErrorsOnAllBut(mNewPasswordLayout); } else { - mCurrentPassword.setError(null); - mNewPassword.setError(null); - mNewPasswordConfirm.setError(null); + mCurrentPasswordLayout.setError(null); + mNewPasswordLayout.setError(null); xmppConnectionService.updateAccountPasswordOnServer(mAccount, newPassword, ChangePasswordActivity.this); mChangePasswordButton.setEnabled(false); mChangePasswordButton.setTextColor(getSecondaryTextColor()); @@ -43,20 +41,18 @@ public class ChangePasswordActivity extends XmppActivity implements XmppConnecti } } }; - private TextView mCurrentPasswordLabel; private EditText mCurrentPassword; private EditText mNewPassword; - private EditText mNewPasswordConfirm; private Account mAccount; + private TextInputLayout mNewPasswordLayout; + private TextInputLayout mCurrentPasswordLayout; @Override void onBackendConnected() { this.mAccount = extractAccount(getIntent()); if (this.mAccount != null && this.mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) { - this.mCurrentPasswordLabel.setVisibility(View.GONE); this.mCurrentPassword.setVisibility(View.GONE); } else { - this.mCurrentPasswordLabel.setVisibility(View.VISIBLE); this.mCurrentPassword.setVisibility(View.VISIBLE); } } @@ -74,10 +70,10 @@ public class ChangePasswordActivity extends XmppActivity implements XmppConnecti }); this.mChangePasswordButton = findViewById(R.id.right_button); this.mChangePasswordButton.setOnClickListener(this.mOnChangePasswordButtonClicked); - this.mCurrentPasswordLabel = findViewById(R.id.current_password_label); this.mCurrentPassword = findViewById(R.id.current_password); + this.mCurrentPassword.setCustomSelectionActionModeCallback(new DisabledActionModeCallback()); this.mNewPassword = findViewById(R.id.new_password); - this.mNewPasswordConfirm = findViewById(R.id.new_password_confirm); + this.mNewPassword.setCustomSelectionActionModeCallback(new DisabledActionModeCallback()); } @Override @@ -104,18 +100,27 @@ public class ChangePasswordActivity extends XmppActivity implements XmppConnecti @Override public void onPasswordChangeFailed() { - runOnUiThread(new Runnable() { - @Override - public void run() { - mNewPassword.setError(getString(R.string.could_not_change_password)); - mChangePasswordButton.setEnabled(true); - mChangePasswordButton.setTextColor(getPrimaryTextColor()); - mChangePasswordButton.setText(R.string.change_password); - } + runOnUiThread(() -> { + mNewPasswordLayout.setError(getString(R.string.could_not_change_password)); + mChangePasswordButton.setEnabled(true); + mChangePasswordButton.setTextColor(getPrimaryTextColor()); + mChangePasswordButton.setText(R.string.change_password); }); } + private void removeErrorsOnAllBut(TextInputLayout exception) { + if (this.mCurrentPasswordLayout != exception) { + this.mCurrentPasswordLayout.setErrorEnabled(false); + this.mCurrentPasswordLayout.setError(null); + } + if (this.mNewPasswordLayout != exception) { + this.mNewPasswordLayout.setErrorEnabled(false); + this.mNewPasswordLayout.setError(null); + } + + } + public void refreshUiReal() { } diff --git a/src/main/java/de/pixart/messenger/ui/ChooseContactActivity.java b/src/main/java/de/pixart/messenger/ui/ChooseContactActivity.java index 9d51a1c1d..011c8b051 100644 --- a/src/main/java/de/pixart/messenger/ui/ChooseContactActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ChooseContactActivity.java @@ -1,10 +1,10 @@ package de.pixart.messenger.ui; -import android.app.ActionBar; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.support.annotation.StringRes; +import android.support.v7.app.ActionBar; import android.view.ActionMode; import android.view.Menu; import android.view.MenuInflater; @@ -140,7 +140,7 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity { Intent intent = getIntent(); @StringRes int res = intent != null ? intent.getIntExtra(EXTRA_TITLE_RES_ID, R.string.title_activity_choose_contact) : R.string.title_activity_choose_contact; - ActionBar bar = getActionBar(); + ActionBar bar = getSupportActionBar(); if (bar != null) { try { bar.setTitle(res); diff --git a/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java b/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java index 21442bcd1..fa77b9423 100644 --- a/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java @@ -1,14 +1,21 @@ package de.pixart.messenger.ui; -import android.app.ActionBar; -import android.app.AlertDialog; import android.app.PendingIntent; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentSender.SendIntentException; +import android.content.res.Resources; +import android.databinding.DataBindingUtil; +import android.graphics.Bitmap; import android.graphics.PorterDuff; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AlertDialog; +import android.support.v7.widget.CardView; import android.util.Log; import android.view.ContextMenu; import android.view.LayoutInflater; @@ -26,13 +33,16 @@ import android.widget.Toast; import org.openintents.openpgp.util.OpenPgpUtils; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; +import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicInteger; import de.pixart.messenger.Config; import de.pixart.messenger.R; import de.pixart.messenger.crypto.PgpEngine; +import de.pixart.messenger.databinding.ContactBinding; import de.pixart.messenger.entities.Account; import de.pixart.messenger.entities.Bookmark; import de.pixart.messenger.entities.Contact; @@ -42,6 +52,7 @@ import de.pixart.messenger.entities.MucOptions.User; import de.pixart.messenger.services.XmppConnectionService; import de.pixart.messenger.services.XmppConnectionService.OnConversationUpdate; import de.pixart.messenger.services.XmppConnectionService.OnMucRosterUpdate; +import de.pixart.messenger.utils.UIHelper; import de.pixart.messenger.xmpp.jid.Jid; public class ConferenceDetailsActivity extends XmppActivity implements OnConversationUpdate, OnMucRosterUpdate, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnRoleChanged, XmppConnectionService.OnConfigurationPushed { @@ -83,10 +94,10 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers private TextView mFullJid; private TextView mAccountJid; private LinearLayout membersView; - private LinearLayout mMoreDetails; + private CardView mMoreDetails; private RelativeLayout mMucSettings; private TextView mConferenceType; - private LinearLayout mConferenceInfoTable; + private CardView mConferenceInfoTable; private TextView mConferenceInfoMam; private TextView mNotifyStatusText; private ImageButton mChangeConferenceSettingsButton; @@ -294,29 +305,19 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers mDestroyButton.setOnClickListener(destroyListener); mDestroyButton.getBackground().setColorFilter(getWarningButtonColor(), PorterDuff.Mode.MULTIPLY); mConferenceType = findViewById(R.id.muc_conference_type); - if (getActionBar() != null) { - getActionBar().setHomeButtonEnabled(true); - getActionBar().setDisplayHomeAsUpEnabled(true); - } - mEditNickButton.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - quickEdit(mConversation.getMucOptions().getActualNick(), - 0, - new OnValueEdited() { - - @Override - public String onValueEdited(String value) { - if (xmppConnectionService.renameInMuc(mConversation, value, renameCallback)) { - return null; - } else { - return getString(R.string.invalid_username); - } - } - }); - } - }); + if (getSupportActionBar() != null) { + getSupportActionBar().setHomeButtonEnabled(true); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + } + mEditNickButton.setOnClickListener(v -> quickEdit(mConversation.getMucOptions().getActualNick(), + 0, + value -> { + if (xmppConnectionService.renameInMuc(mConversation, value, renameCallback)) { + return null; + } else { + return getString(R.string.invalid_username); + } + })); this.mAdvancedMode = getPreferences().getBoolean("advanced_muc_mode", false); this.mConferenceInfoTable = findViewById(R.id.muc_info_more); this.mConferenceInfoTable.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE); @@ -600,17 +601,19 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } else { account = mConversation.getAccount().getJid().toBareJid().toString(); } - if (getActionBar() != null) { - final ActionBar ab = getActionBar(); - ab.setCustomView(R.layout.ab_title); - ab.setDisplayShowCustomEnabled(true); - TextView abtitle = findViewById(android.R.id.text1); - TextView absubtitle = findViewById(android.R.id.text2); - abtitle.setText(mConversation.getName()); - abtitle.setSelected(true); - abtitle.setClickable(false); - absubtitle.setVisibility(View.GONE); - absubtitle.setClickable(false); + if (getSupportActionBar() != null) { + final ActionBar ab = getSupportActionBar(); + if (ab != null) { + ab.setCustomView(R.layout.ab_title); + ab.setDisplayShowCustomEnabled(true); + TextView abtitle = findViewById(android.R.id.text1); + TextView absubtitle = findViewById(android.R.id.text2); + abtitle.setText(mConversation.getName()); + abtitle.setSelected(true); + abtitle.setClickable(false); + absubtitle.setVisibility(View.GONE); + absubtitle.setClickable(false); + } } ConferenceName.setText(mConversation.getName()); mAccountJid.setText(getString(R.string.using_account, account)); @@ -668,56 +671,44 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers mNotifyStatusText.setText(R.string.notify_only_when_highlighted); } - LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); + final LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); membersView.removeAllViews(); + if (inflater == null) { + return; + } final ArrayList<User> users = mucOptions.getUsers(); Collections.sort(users); for (final User user : users) { + ContactBinding binding = DataBindingUtil.inflate(inflater, R.layout.contact, membersView, false); final Contact contact = user.getContact(); - View view = inflater.inflate(R.layout.contact, membersView, false); - this.setListItemBackgroundOnView(view); + final String name = user.getName(); + this.setListItemBackgroundOnView(binding.getRoot()); if (contact != null && contact.showInRoster()) { - view.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View view) { - switchToContactDetails(contact); - } - }); + binding.getRoot().setOnClickListener((OnClickListener) view -> switchToContactDetails(contact)); } - registerForContextMenu(view); - view.setTag(user); - TextView tvDisplayName = view.findViewById(R.id.contact_display_name); - TextView tvKey = view.findViewById(R.id.key); - TextView tvStatus = view.findViewById(R.id.contact_jid); + registerForContextMenu(binding.getRoot()); + binding.getRoot().setTag(user); if (mAdvancedMode && user.getPgpKeyId() != 0) { - tvKey.setVisibility(View.VISIBLE); - tvKey.setOnClickListener(new OnClickListener() { - - @Override - public void onClick(View v) { - viewPgpKey(user); - } - }); - tvKey.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId())); + binding.key.setVisibility(View.VISIBLE); + binding.key.setOnClickListener(v -> viewPgpKey(user)); + binding.key.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId())); } - String name = user.getName(); if (contact != null) { - tvDisplayName.setText(contact.getDisplayName()); - tvStatus.setText((name != null ? name + " \u2022 " : "") + getStatus(user)); + binding.contactDisplayName.setText(contact.getDisplayName()); + binding.contactJid.setText((name != null ? name + " \u2022 " : "") + getStatus(user)); } else { - tvDisplayName.setText(name == null ? "" : name); - tvStatus.setText(getStatus(user)); + binding.contactDisplayName.setText(name == null ? "" : name); + binding.contactJid.setText(getStatus(user)); } - ImageView iv = view.findViewById(R.id.contact_photo); - iv.setImageBitmap(avatarService().get(user, getPixel(48), false)); + loadAvatar(user, binding.contactPhoto); if (user.getRole() == MucOptions.Role.NONE) { - tvDisplayName.setAlpha(INACTIVE_ALPHA); - tvKey.setAlpha(INACTIVE_ALPHA); - tvStatus.setAlpha(INACTIVE_ALPHA); - iv.setAlpha(INACTIVE_ALPHA); + binding.contactDisplayName.setAlpha(INACTIVE_ALPHA); + binding.key.setAlpha(INACTIVE_ALPHA); + binding.contactJid.setAlpha(INACTIVE_ALPHA); + binding.contactPhoto.setAlpha(INACTIVE_ALPHA); } - membersView.addView(view); + membersView.addView(binding.getRoot()); if (mConversation.getMucOptions().canInvite()) { mInviteButton.setVisibility(View.VISIBLE); } else { @@ -780,11 +771,95 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } private void displayToast(final String msg) { - runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(ConferenceDetailsActivity.this, msg, Toast.LENGTH_SHORT).show(); + runOnUiThread(() -> Toast.makeText(ConferenceDetailsActivity.this, msg, Toast.LENGTH_SHORT).show()); + } + + public static boolean cancelPotentialWork(User user, ImageView imageView) { + final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + + if (bitmapWorkerTask != null) { + final User old = bitmapWorkerTask.o; + if (old == null || user != old) { + bitmapWorkerTask.cancel(true); + } else { + return false; + } + } + return true; + } + + private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { + if (imageView != null) { + final Drawable drawable = imageView.getDrawable(); + if (drawable instanceof AsyncDrawable) { + final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; + return asyncDrawable.getBitmapWorkerTask(); } - }); + } + return null; + } + + public void loadAvatar(User user, ImageView imageView) { + if (cancelPotentialWork(user, imageView)) { + final Bitmap bm = avatarService().get(user, getPixel(48), true); + if (bm != null) { + cancelPotentialWork(user, imageView); + imageView.setImageBitmap(bm); + imageView.setBackgroundColor(0x00000000); + } else { + String seed = user.getRealJid() != null ? user.getRealJid().toBareJid().toString() : null; + imageView.setBackgroundColor(UIHelper.getColorForName(seed == null ? user.getName() : seed)); + imageView.setImageDrawable(null); + final BitmapWorkerTask task = new BitmapWorkerTask(imageView); + final AsyncDrawable asyncDrawable = new AsyncDrawable(getResources(), null, task); + imageView.setImageDrawable(asyncDrawable); + try { + task.execute(user); + } catch (final RejectedExecutionException ignored) { + } + } + } + } + + static class AsyncDrawable extends BitmapDrawable { + private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference; + + public AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) { + super(res, bitmap); + bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask); + } + + public BitmapWorkerTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } + } + + class BitmapWorkerTask extends AsyncTask<User, Void, Bitmap> { + private final WeakReference<ImageView> imageViewReference; + private User o = null; + + private BitmapWorkerTask(ImageView imageView) { + imageViewReference = new WeakReference<>(imageView); + } + + @Override + protected Bitmap doInBackground(User... params) { + this.o = params[0]; + if (imageViewReference.get() == null) { + return null; + } + return avatarService().get(this.o, getPixel(48), isCancelled()); + } + + @Override + protected void onPostExecute(Bitmap bitmap) { + if (bitmap != null && !isCancelled()) { + final ImageView imageView = imageViewReference.get(); + if (imageView != null) { + imageView.setImageBitmap(bitmap); + imageView.setBackgroundColor(0x00000000); + } + } + } } } diff --git a/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java b/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java index ca435d6a5..824546266 100644 --- a/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java @@ -1,11 +1,10 @@ package de.pixart.messenger.ui; -import android.app.ActionBar; -import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.databinding.DataBindingUtil; import android.graphics.PorterDuff; import android.net.Uri; import android.os.Bundle; @@ -14,23 +13,19 @@ import android.provider.ContactsContract.CommonDataKinds; import android.provider.ContactsContract.Contacts; import android.provider.ContactsContract.Intents; import android.support.v4.content.ContextCompat; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AlertDialog; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; -import android.widget.Button; -import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.QuickContactBadge; import android.widget.TextView; import android.widget.Toast; -import com.wefika.flowlayout.FlowLayout; - import org.openintents.openpgp.util.OpenPgpUtils; import java.util.List; @@ -51,6 +46,7 @@ import de.pixart.messenger.utils.CryptoHelper; import de.pixart.messenger.utils.Namespace; import de.pixart.messenger.utils.UIHelper; import de.pixart.messenger.utils.XmppUri; +import de.pixart.messenger.databinding.ActivityContactDetailsBinding; import de.pixart.messenger.xmpp.OnKeyStatusUpdated; import de.pixart.messenger.xmpp.OnUpdateBlocklist; import de.pixart.messenger.xmpp.XmppConnection; @@ -109,22 +105,11 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp } } }; + + ActivityContactDetailsBinding binding; + private Jid accountJid; - private TextView lastseen; private Jid contactJid; - private TextView contactDisplayName; - private TextView contactJidTv; - private TextView accountJidTv; - private TextView statusMessage; - private TextView resource; - private CheckBox send; - private CheckBox receive; - private Button addContactButton; - private Button mShowInactiveDevicesButton; - private QuickContactBadge badge; - private LinearLayout keys; - private LinearLayout keysWrapper; - private FlowLayout tags; private boolean showDynamicTags = false; private boolean showLastSeen = false; private boolean showInactiveOmemo = false; @@ -272,32 +257,15 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp } } this.messageFingerprint = getIntent().getStringExtra("fingerprint"); - setContentView(R.layout.activity_contact_details); - - contactDisplayName = findViewById(R.id.contact_display_name); - contactJidTv = findViewById(R.id.details_contactjid); - accountJidTv = findViewById(R.id.details_account); - lastseen = findViewById(R.id.details_lastseen); - statusMessage = findViewById(R.id.status_message); - resource = findViewById(R.id.resource); - send = findViewById(R.id.details_send_presence); - receive = findViewById(R.id.details_receive_presence); - badge = findViewById(R.id.details_contact_badge); - addContactButton = findViewById(R.id.add_contact_button); - keys = findViewById(R.id.details_contact_keys); - keysWrapper = findViewById(R.id.keys_wrapper); - tags = findViewById(R.id.tags); - mShowInactiveDevicesButton = findViewById(R.id.show_inactive_devices); - if (getActionBar() != null) { - getActionBar().setHomeButtonEnabled(true); - getActionBar().setDisplayHomeAsUpEnabled(true); + + this.binding = DataBindingUtil.setContentView(this, R.layout.activity_contact_details); + if (getSupportActionBar() != null) { + getSupportActionBar().setHomeButtonEnabled(true); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); } - mShowInactiveDevicesButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - showInactiveOmemo = !showInactiveOmemo; - populateView(); - } + binding.showInactiveDevices.setOnClickListener(v -> { + showInactiveOmemo = !showInactiveOmemo; + populateView(); }); this.mNotifyStatusButton = findViewById(R.id.notification_status_button); this.mNotifyStatusButton.setOnClickListener(this.mNotifyStatusClickListener); @@ -409,34 +377,39 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp mNotifyStatusButton.setImageResource(R.drawable.ic_notifications_grey600_24dp); mNotifyStatusText.setText(R.string.notify_on_all_messages); } - if (getActionBar() != null) { - final ActionBar ab = getActionBar(); - ab.setCustomView(R.layout.ab_title); - ab.setDisplayShowCustomEnabled(true); - TextView abtitle = findViewById(android.R.id.text1); - TextView absubtitle = findViewById(android.R.id.text2); - abtitle.setText(contact.getDisplayName()); - abtitle.setSelected(true); - abtitle.setClickable(false); - absubtitle.setVisibility(View.GONE); - absubtitle.setClickable(false); + if (getSupportActionBar() != null) { + final ActionBar ab = getSupportActionBar(); + if (ab != null) { + ab.setCustomView(R.layout.ab_title); + ab.setDisplayShowCustomEnabled(true); + TextView abtitle = findViewById(android.R.id.text1); + TextView absubtitle = findViewById(android.R.id.text2); + abtitle.setText(contact.getDisplayName()); + abtitle.setSelected(true); + abtitle.setClickable(false); + absubtitle.setVisibility(View.GONE); + absubtitle.setClickable(false); + } } invalidateOptionsMenu(); setTitle(contact.getDisplayName()); if (contact.getServer().toString().toLowerCase().equals(accountJid.getDomainpart().toLowerCase())) { - contactDisplayName.setText(contact.getDisplayName()); + binding.contactDisplayName.setText(contact.getDisplayName()); } else { - contactDisplayName.setText(contact.getDisplayJid()); + binding.contactDisplayName.setText(contact.getDisplayJid()); } if (contact.showInRoster()) { - send.setVisibility(View.VISIBLE); - receive.setVisibility(View.VISIBLE); - addContactButton.setVisibility(View.VISIBLE); - addContactButton.setText(getString(R.string.action_delete_contact)); - addContactButton.getBackground().setColorFilter(getWarningButtonColor(), PorterDuff.Mode.MULTIPLY); + binding.detailsSendPresence.setVisibility(View.VISIBLE); + binding.detailsReceivePresence.setVisibility(View.VISIBLE); + binding.addContactButton.setVisibility(View.VISIBLE); + binding.addContactButton.setText(getString(R.string.action_delete_contact)); + binding.addContactButton.getBackground().setColorFilter(getWarningButtonColor(), PorterDuff.Mode.MULTIPLY); + binding.detailsSendPresence.setOnCheckedChangeListener(null); + binding.detailsReceivePresence.setOnCheckedChangeListener(null); + final AlertDialog.Builder deleteFromRosterDialog = new AlertDialog.Builder(ContactDetailsActivity.this); - addContactButton.setOnClickListener(new OnClickListener() { + binding.addContactButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { deleteFromRosterDialog.setNegativeButton(getString(R.string.cancel), null); @@ -448,15 +421,15 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp removeFromRoster).create().show(); } }); - send.setOnCheckedChangeListener(null); - receive.setOnCheckedChangeListener(null); + binding.detailsSendPresence.setOnCheckedChangeListener(null); + binding.detailsReceivePresence.setOnCheckedChangeListener(null); List<String> statusMessages = contact.getPresences().getStatusMessages(); if (statusMessages.size() == 0) { - statusMessage.setVisibility(View.GONE); + binding.statusMessage.setVisibility(View.GONE); } else { StringBuilder builder = new StringBuilder(); - statusMessage.setVisibility(View.VISIBLE); + binding.statusMessage.setVisibility(View.VISIBLE); int s = statusMessages.size(); for (int i = 0; i < s; ++i) { if (s > 1) { @@ -467,85 +440,85 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp builder.append("\n"); } } - statusMessage.setText(builder); + binding.statusMessage.setText(builder); } String resources = contact.getPresences().getMostAvailableResource(); if (resources.length() == 0) { - resource.setVisibility(View.GONE); + binding.resource.setVisibility(View.GONE); } else { - resource.setVisibility(View.VISIBLE); - resource.setText(resources); + binding.resource.setVisibility(View.VISIBLE); + binding.resource.setText(resources); } if (contact.getOption(Contact.Options.FROM)) { - send.setText(R.string.send_presence_updates); - send.setChecked(true); + binding.detailsSendPresence.setText(R.string.send_presence_updates); + binding.detailsSendPresence.setChecked(true); } else if (contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) { - send.setChecked(false); - send.setText(R.string.send_presence_updates); + binding.detailsSendPresence.setChecked(false); + binding.detailsSendPresence.setText(R.string.send_presence_updates); } else { - send.setText(R.string.preemptively_grant); + binding.detailsSendPresence.setText(R.string.preemptively_grant); if (contact.getOption(Contact.Options.PREEMPTIVE_GRANT)) { - send.setChecked(true); + binding.detailsSendPresence.setChecked(true); } else { - send.setChecked(false); + binding.detailsSendPresence.setChecked(false); } } if (contact.getOption(Contact.Options.TO)) { - receive.setText(R.string.receive_presence_updates); - receive.setChecked(true); + binding.detailsReceivePresence.setText(R.string.receive_presence_updates); + binding.detailsReceivePresence.setChecked(true); } else { - receive.setText(R.string.ask_for_presence_updates); + binding.detailsReceivePresence.setText(R.string.ask_for_presence_updates); if (contact.getOption(Contact.Options.ASKING)) { - receive.setChecked(true); + binding.detailsReceivePresence.setChecked(true); } else { - receive.setChecked(false); + binding.detailsReceivePresence.setChecked(false); } } if (contact.getAccount().isOnlineAndConnected()) { - receive.setEnabled(true); - send.setEnabled(true); + binding.detailsReceivePresence.setEnabled(true); + binding.detailsSendPresence.setEnabled(true); } else { - receive.setEnabled(false); - send.setEnabled(false); + binding.detailsReceivePresence.setEnabled(false); + binding.detailsSendPresence.setEnabled(false); } - send.setOnCheckedChangeListener(this.mOnSendCheckedChange); - receive.setOnCheckedChangeListener(this.mOnReceiveCheckedChange); + binding.detailsSendPresence.setOnCheckedChangeListener(this.mOnSendCheckedChange); + binding.detailsReceivePresence.setOnCheckedChangeListener(this.mOnReceiveCheckedChange); } else { - addContactButton.setVisibility(View.VISIBLE); - addContactButton.setText(getString(R.string.add_contact)); - addContactButton.getBackground().clearColorFilter(); - addContactButton.setOnClickListener(new OnClickListener() { + binding.addContactButton.setVisibility(View.VISIBLE); + binding.addContactButton.setText(getString(R.string.add_contact)); + binding.addContactButton.getBackground().clearColorFilter(); + binding.addContactButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View view) { showAddToRosterDialog(contact); } }); - send.setVisibility(View.GONE); - receive.setVisibility(View.GONE); - statusMessage.setVisibility(View.GONE); + binding.detailsSendPresence.setVisibility(View.GONE); + binding.detailsReceivePresence.setVisibility(View.GONE); + binding.statusMessage.setVisibility(View.GONE); } if (contact.isBlocked() && !this.showDynamicTags) { - lastseen.setVisibility(View.VISIBLE); - lastseen.setText(R.string.contact_blocked); + binding.detailsLastseen.setVisibility(View.VISIBLE); + binding.detailsLastseen.setText(R.string.contact_blocked); } else { if (showLastSeen && contact.getLastseen() > 0 && contact.getPresences().allOrNonSupport(Namespace.IDLE)) { - lastseen.setVisibility(View.VISIBLE); - lastseen.setText(UIHelper.lastseen(getApplicationContext(), contact.isActive(), contact.getLastseen())); + binding.detailsLastseen.setVisibility(View.VISIBLE); + binding.detailsLastseen.setText(UIHelper.lastseen(getApplicationContext(), contact.isActive(), contact.getLastseen())); } else { - lastseen.setVisibility(View.GONE); + binding.detailsLastseen.setVisibility(View.GONE); } } if (contact.getPresences().size() > 1) { - contactJidTv.setText(contact.getDisplayJid() + " (" + binding.detailsContactjid.setText(contact.getDisplayJid() + " (" + contact.getPresences().size() + ")"); } else { - contactJidTv.setText(contact.getDisplayJid()); + binding.detailsContactjid.setText(contact.getDisplayJid()); } String account; if (Config.DOMAIN_LOCK != null) { @@ -553,17 +526,17 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp } else { account = contact.getAccount().getJid().toBareJid().toString(); } - accountJidTv.setText(getString(R.string.using_account, account)); - badge.setImageBitmap(avatarService().get(contact, getPixel(Config.AVATAR_SIZE))); - badge.setOnClickListener(this.onBadgeClick); + binding.detailsAccount.setText(getString(R.string.using_account, account)); + binding.detailsContactBadge.setImageBitmap(avatarService().get(contact, getPixel(Config.AVATAR_SIZE))); + binding.detailsContactBadge.setOnClickListener(this.onBadgeClick); - keys.removeAllViews(); + binding.detailsContactKeys.removeAllViews(); boolean hasKeys = false; LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE); if (Config.supportOtr()) { for (final String otrFingerprint : contact.getOtrFingerprints()) { hasKeys = true; - View view = inflater.inflate(R.layout.contact_key, keys, false); + View view = inflater.inflate(R.layout.contact_key, binding.detailsContactKeys, false); TextView key = view.findViewById(R.id.key); TextView keyType = view.findViewById(R.id.key_type); ImageButton removeButton = view @@ -576,7 +549,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp } else { keyType.setText(R.string.otr_fingerprint); } - keys.addView(view); + binding.detailsContactKeys.addView(view); removeButton.setOnClickListener(new OnClickListener() { @Override @@ -603,21 +576,21 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp } if (!trust.isCompromised()) { boolean highlight = session.getFingerprint().equals(messageFingerprint); - addFingerprintRow(keys, session, highlight); + addFingerprintRow(binding.detailsContactKeys, session, highlight); } } if (showsInactive || skippedInactive) { - mShowInactiveDevicesButton.setText(showsInactive ? R.string.hide_inactive_devices : R.string.show_inactive_devices); - mShowInactiveDevicesButton.setVisibility(View.VISIBLE); + binding.showInactiveDevices.setText(showsInactive ? R.string.hide_inactive_devices : R.string.show_inactive_devices); + binding.showInactiveDevices.setVisibility(View.VISIBLE); } else { - mShowInactiveDevicesButton.setVisibility(View.GONE); + binding.showInactiveDevices.setVisibility(View.GONE); } } else { - mShowInactiveDevicesButton.setVisibility(View.GONE); + binding.showInactiveDevices.setVisibility(View.GONE); } if (Config.supportOpenPgp() && contact.getPgpKeyId() != 0) { hasKeys = true; - View view = inflater.inflate(R.layout.contact_key, keys, false); + View view = inflater.inflate(R.layout.contact_key, binding.detailsContactKeys, false); TextView key = view.findViewById(R.id.key); TextView keyType = view.findViewById(R.id.key_type); keyType.setText(R.string.openpgp_key_id); @@ -635,21 +608,21 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp view.setOnClickListener(openKey); key.setOnClickListener(openKey); keyType.setOnClickListener(openKey); - keys.addView(view); + binding.detailsContactKeys.addView(view); } - keysWrapper.setVisibility(hasKeys ? View.VISIBLE : View.GONE); + binding.keysWrapper.setVisibility(hasKeys ? View.VISIBLE : View.GONE); List<ListItem.Tag> tagList = contact.getTags(this); if (tagList.size() == 0 || !this.showDynamicTags) { - tags.setVisibility(View.GONE); + binding.tags.setVisibility(View.GONE); } else { - tags.setVisibility(View.VISIBLE); - tags.removeAllViewsInLayout(); + binding.tags.setVisibility(View.VISIBLE); + binding.tags.removeAllViewsInLayout(); for (final ListItem.Tag tag : tagList) { - final TextView tv = (TextView) inflater.inflate(R.layout.list_item_tag, tags, false); + final TextView tv = (TextView) inflater.inflate(R.layout.list_item_tag, binding.tags, false); tv.setText(tag.getName()); tv.setBackgroundColor(tag.getColor()); - tags.addView(tv); + binding.tags.addView(tv); } } } diff --git a/src/main/java/de/pixart/messenger/ui/ConversationActivity.java b/src/main/java/de/pixart/messenger/ui/ConversationActivity.java index 192979083..dbfc61a0c 100644 --- a/src/main/java/de/pixart/messenger/ui/ConversationActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ConversationActivity.java @@ -1,8 +1,6 @@ package de.pixart.messenger.ui; import android.annotation.SuppressLint; -import android.app.ActionBar; -import android.app.AlertDialog; import android.app.FragmentTransaction; import android.app.PendingIntent; import android.content.ActivityNotFoundException; @@ -18,16 +16,17 @@ import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Typeface; -import android.media.ExifInterface; import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.os.Handler; import android.preference.PreferenceManager; import android.provider.MediaStore; import android.provider.Settings; +import android.support.media.ExifInterface; import android.support.v4.widget.SlidingPaneLayout; import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AlertDialog; import android.util.Log; import android.util.Pair; import android.view.Gravity; @@ -250,7 +249,7 @@ public class ConversationActivity extends XmppActivity this.listAdapter = new ConversationAdapter(this, conversationList); listView.setAdapter(this.listAdapter); - final ActionBar actionBar = getActionBar(); + final ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE); } @@ -350,12 +349,9 @@ public class ConversationActivity extends XmppActivity @Override public void switchToConversation(Conversation conversation) { setSelectedConversation(conversation); - runOnUiThread(new Runnable() { - @Override - public void run() { - ConversationActivity.this.mConversationFragment.reInit(getSelectedConversation()); - openConversation(); - } + runOnUiThread(() -> { + ConversationActivity.this.mConversationFragment.reInit(getSelectedConversation()); + openConversation(); }); } @@ -364,7 +360,7 @@ public class ConversationActivity extends XmppActivity } private void updateActionBarTitle(boolean titleShouldBeName) { - final ActionBar ab = getActionBar(); + final ActionBar ab = getSupportActionBar(); final Conversation conversation = getSelectedConversation(); if (ab != null) { if (titleShouldBeName && conversation != null) { @@ -481,6 +477,8 @@ public class ConversationActivity extends XmppActivity final MenuItem menuClearHistory = menu.findItem(R.id.action_clear_history); final MenuItem menuAdd = menu.findItem(R.id.action_add); final MenuItem menuInviteContact = menu.findItem(R.id.action_invite); + final MenuItem menuAttachSoundRecorder = menu.findItem(R.id.attach_record_voice); + final MenuItem menuAttachLocation = menu.findItem(R.id.attach_location); final MenuItem menuUpdater = menu.findItem(R.id.action_check_updates); final MenuItem menuInviteUser = menu.findItem(R.id.action_invite_user); final MenuItem menuSearchHistory = menu.findItem(R.id.action_search_history); @@ -498,6 +496,8 @@ public class ConversationActivity extends XmppActivity menuSecure.setVisible(false); menuInviteContact.setVisible(false); menuAttach.setVisible(false); + menuAttachLocation.setVisible(false); + menuAttachSoundRecorder.setVisible(false); menuClearHistory.setVisible(false); menuSearchHistory.setVisible(false); if (xmppConnectionService.installedFromFDroid()) { @@ -518,11 +518,7 @@ public class ConversationActivity extends XmppActivity menuArchiveChat.setVisible(false); } if (this.getSelectedConversation().getNextEncryption() != Message.ENCRYPTION_NONE) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - menuSecure.setIcon(R.drawable.ic_lock_white_24dp); - } else { - menuSecure.setIcon(R.drawable.ic_action_secure); - } + menuSecure.setIcon(R.drawable.ic_lock_white_24dp); } if (this.getSelectedConversation().getMode() == Conversation.MODE_MULTI) { menuAttach.setVisible(getSelectedConversation().getAccount().httpUploadAvailable() && getSelectedConversation().getMucOptions().participating()); @@ -532,133 +528,119 @@ public class ConversationActivity extends XmppActivity menuSecure.setVisible(Config.multipleEncryptionChoices()); menuInviteContact.setVisible(xmppConnectionService != null && xmppConnectionService.findConferenceServer(getSelectedConversation().getAccount()) != null); } + menuAttachLocation.setVisible(true); + menuAttachSoundRecorder.setVisible(true); + configureEncryptionMenu(getSelectedConversation(), menu); } } - if (Config.supportOmemo()) { - new Handler().post(addOmemoDebuggerRunnable); - } return super.onCreateOptionsMenu(menu); } - private Runnable addOmemoDebuggerRunnable = new Runnable() { - @Override - public void run() { - View view = findViewById(R.id.action_security); - if (view != null) { - view.setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - return v.getId() == R.id.action_security && quickOmemoDebugger(getSelectedConversation()); - } - }); - } - } - }; - - private boolean quickOmemoDebugger(Conversation c) { - if (c != null) { - boolean single = c.getMode() == Conversation.MODE_SINGLE; - AxolotlService axolotlService = c.getAccount().getAxolotlService(); - Pair<AxolotlService.AxolotlCapability, Jid> capabilityJidPair = axolotlService.isConversationAxolotlCapableDetailed(c); - switch (capabilityJidPair.first) { - case MISSING_PRESENCE: - Toast.makeText(ConversationActivity.this, single ? getString(R.string.missing_presence_subscription) : getString(R.string.missing_presence_subscription_with_x, capabilityJidPair.second.toBareJid().toString()), Toast.LENGTH_SHORT).show(); - return true; - case MISSING_KEYS: - Toast.makeText(ConversationActivity.this, single ? getString(R.string.missing_omemo_keys) : getString(R.string.missing_keys_from_x, capabilityJidPair.second.toBareJid().toString()), Toast.LENGTH_SHORT).show(); - return true; - case WRONG_CONFIGURATION: - Toast.makeText(ConversationActivity.this, R.string.wrong_conference_configuration, Toast.LENGTH_SHORT).show(); - return true; - case NO_MEMBERS: - Toast.makeText(ConversationActivity.this, R.string.this_conference_has_no_members, Toast.LENGTH_SHORT).show(); - return true; - } + private static void configureEncryptionMenu(Conversation conversation, Menu menu) { + MenuItem otr = menu.findItem(R.id.encryption_choice_otr); + MenuItem none = menu.findItem(R.id.encryption_choice_none); + MenuItem pgp = menu.findItem(R.id.encryption_choice_pgp); + MenuItem axolotl = menu.findItem(R.id.encryption_choice_axolotl); + pgp.setVisible(Config.supportOpenPgp()); + none.setVisible(Config.supportUnencrypted() || conversation.getMode() == Conversation.MODE_MULTI); + otr.setVisible(Config.supportOtr()); + axolotl.setVisible(Config.supportOmemo()); + if (conversation.getMode() == Conversation.MODE_MULTI) { + otr.setVisible(false); + } + if (!conversation.getAccount().getAxolotlService().isConversationAxolotlCapable(conversation)) { + axolotl.setEnabled(false); + } + switch (conversation.getNextEncryption()) { + case Message.ENCRYPTION_NONE: + none.setChecked(true); + break; + case Message.ENCRYPTION_OTR: + otr.setChecked(true); + break; + case Message.ENCRYPTION_PGP: + pgp.setChecked(true); + break; + case Message.ENCRYPTION_AXOLOTL: + axolotl.setChecked(true); + break; + default: + none.setChecked(true); + break; } - return false; } protected void selectPresenceToAttachFile(final int attachmentChoice, final int encryption) { final Conversation conversation = getSelectedConversation(); final Account account = conversation.getAccount(); - final OnPresenceSelected callback = new OnPresenceSelected() { - - @Override - public void onPresenceSelected() { - final Intent intent = new Intent(); - boolean chooser = false; - String fallbackPackageId = null; - switch (attachmentChoice) { - case ATTACHMENT_CHOICE_CHOOSE_IMAGE: - intent.setAction(Intent.ACTION_GET_CONTENT); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); - } - intent.setType("image/*"); - chooser = true; - break; - case ATTACHMENT_CHOICE_CHOOSE_VIDEO: - chooser = true; - intent.setType("video/*"); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setAction(Intent.ACTION_GET_CONTENT); - break; - case ATTACHMENT_CHOICE_TAKE_FROM_CAMERA: - AlertDialog.Builder builder = new AlertDialog.Builder(ConversationActivity.this); - builder.setTitle(getString(R.string.attach_take_from_camera)); - builder.setNegativeButton(getString(R.string.action_take_photo), - new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - Uri uri = xmppConnectionService.getFileBackend().getTakePhotoUri(); - intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); - intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); - mPendingPhotoUris.clear(); - mPendingPhotoUris.add(uri); - startActivityForResult(intent, attachmentChoice); - } - }); - builder.setPositiveButton(getString(R.string.action_take_video), - new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - Uri uri = xmppConnectionService.getFileBackend().getTakeVideoUri(); - intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - intent.setAction(MediaStore.ACTION_VIDEO_CAPTURE); - mPendingFileUris.clear(); - mPendingFileUris.add(uri); - startActivityForResult(intent, attachmentChoice); - } - }); - builder.create().show(); - break; - case ATTACHMENT_CHOICE_CHOOSE_FILE: - chooser = true; - intent.setType("*/*"); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setAction(Intent.ACTION_GET_CONTENT); - break; - case ATTACHMENT_CHOICE_RECORD_VOICE: - startActivityForResult(new Intent(getApplicationContext(), RecordingActivity.class), attachmentChoice); - break; - case ATTACHMENT_CHOICE_LOCATION: - startActivityForResult(new Intent(getApplicationContext(), ShareLocationActivity.class), attachmentChoice); - break; - } - if (intent.resolveActivity(getPackageManager()) != null) { - Log.d(Config.LOGTAG, "Attachment: " + attachmentChoice); - if (chooser) { - startActivityForResult( - Intent.createChooser(intent, getString(R.string.perform_action_with)), - attachmentChoice); - } else { - startActivityForResult(intent, attachmentChoice); + final OnPresenceSelected callback = () -> { + final Intent intent = new Intent(); + boolean chooser = false; + String fallbackPackageId = null; + switch (attachmentChoice) { + case ATTACHMENT_CHOICE_CHOOSE_IMAGE: + intent.setAction(Intent.ACTION_GET_CONTENT); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); } - } else if (fallbackPackageId != null) { - startActivity(getInstallApkIntent(fallbackPackageId)); + intent.setType("image/*"); + chooser = true; + break; + case ATTACHMENT_CHOICE_CHOOSE_VIDEO: + chooser = true; + intent.setType("video/*"); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setAction(Intent.ACTION_GET_CONTENT); + break; + case ATTACHMENT_CHOICE_TAKE_FROM_CAMERA: + AlertDialog.Builder builder = new AlertDialog.Builder(ConversationActivity.this); + builder.setTitle(getString(R.string.attach_take_from_camera)); + builder.setNegativeButton(getString(R.string.action_take_photo), + (dialog, which) -> { + Uri uri = xmppConnectionService.getFileBackend().getTakePhotoUri(); + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE); + intent.putExtra(MediaStore.EXTRA_OUTPUT, uri); + mPendingPhotoUris.clear(); + mPendingPhotoUris.add(uri); + startActivityForResult(intent, attachmentChoice); + }); + builder.setPositiveButton(getString(R.string.action_take_video), + (dialog, which) -> { + Uri uri = xmppConnectionService.getFileBackend().getTakeVideoUri(); + intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.setAction(MediaStore.ACTION_VIDEO_CAPTURE); + mPendingFileUris.clear(); + mPendingFileUris.add(uri); + startActivityForResult(intent, attachmentChoice); + }); + builder.create().show(); + break; + case ATTACHMENT_CHOICE_CHOOSE_FILE: + chooser = true; + intent.setType("*/*"); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setAction(Intent.ACTION_GET_CONTENT); + break; + case ATTACHMENT_CHOICE_RECORD_VOICE: + startActivityForResult(new Intent(getApplicationContext(), RecordingActivity.class), attachmentChoice); + break; + case ATTACHMENT_CHOICE_LOCATION: + startActivityForResult(new Intent(getApplicationContext(), ShareLocationActivity.class), attachmentChoice); + break; + } + if (intent.resolveActivity(getPackageManager()) != null) { + Log.d(Config.LOGTAG, "Attachment: " + attachmentChoice); + if (chooser) { + startActivityForResult( + Intent.createChooser(intent, getString(R.string.perform_action_with)), + attachmentChoice); + } else { + startActivityForResult(intent, attachmentChoice); } + } else if (fallbackPackageId != null) { + startActivity(getInstallApkIntent(fallbackPackageId)); } }; if ((account.httpUploadAvailable() || attachmentChoice == ATTACHMENT_CHOICE_LOCATION) && encryption != Message.ENCRYPTION_OTR) { @@ -817,8 +799,26 @@ public class ConversationActivity extends XmppActivity return true; } else if (getSelectedConversation() != null) { switch (item.getItemId()) { - case R.id.action_attach_file: - attachFileDialog(); + case R.id.encryption_choice_axolotl: + case R.id.encryption_choice_otr: + case R.id.encryption_choice_pgp: + case R.id.encryption_choice_none: + handleEncryptionSelection(item); + break; + case R.id.attach_choose_picture: + attachFile(ATTACHMENT_CHOICE_CHOOSE_IMAGE); + break; + case R.id.attach_take_picture: + attachFile(ATTACHMENT_CHOICE_TAKE_FROM_CAMERA); + break; + case R.id.attach_choose_file: + attachFile(ATTACHMENT_CHOICE_CHOOSE_FILE); + break; + case R.id.attach_record_voice: + attachFile(ATTACHMENT_CHOICE_RECORD_VOICE); + break; + case R.id.attach_location: + attachFile(ATTACHMENT_CHOICE_LOCATION); break; case R.id.action_archive_chat: this.endConversation(getSelectedConversation()); @@ -840,9 +840,6 @@ public class ConversationActivity extends XmppActivity case R.id.action_invite: inviteToConversation(getSelectedConversation()); break; - case R.id.action_security: - selectEncryptionDialog(getSelectedConversation()); - break; case R.id.action_clear_history: clearHistoryDialog(getSelectedConversation()); break; @@ -893,10 +890,8 @@ public class ConversationActivity extends XmppActivity protected void clearHistoryDialog(final Conversation conversation) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(getString(R.string.clear_conversation_history)); - View dialogView = getLayoutInflater().inflate( - R.layout.dialog_clear_history, null); - final CheckBox endConversationCheckBox = dialogView - .findViewById(R.id.end_conversation_checkbox); + final View dialogView = getLayoutInflater().inflate(R.layout.dialog_clear_history, null); + final CheckBox endConversationCheckBox = dialogView.findViewById(R.id.end_conversation_checkbox); if (conversation.getMode() == Conversation.MODE_SINGLE) { endConversationCheckBox.setVisibility(View.VISIBLE); endConversationCheckBox.setChecked(true); @@ -904,65 +899,23 @@ public class ConversationActivity extends XmppActivity builder.setView(dialogView); builder.setNegativeButton(getString(R.string.cancel), null); builder.setPositiveButton(getString(R.string.delete_messages), - new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - ConversationActivity.this.xmppConnectionService.clearConversationHistory(conversation); - if (conversation.getMode() == Conversation.MODE_SINGLE) { - if (endConversationCheckBox.isChecked()) { - endConversation(conversation); - } else { - updateConversationList(); - ConversationActivity.this.mConversationFragment.updateMessages(); - } + (dialog, which) -> { + ConversationActivity.this.xmppConnectionService.clearConversationHistory(conversation); + if (conversation.getMode() == Conversation.MODE_SINGLE) { + if (endConversationCheckBox.isChecked()) { + endConversation(conversation); } else { updateConversationList(); ConversationActivity.this.mConversationFragment.updateMessages(); } + } else { + updateConversationList(); + ConversationActivity.this.mConversationFragment.updateMessages(); } }); builder.create().show(); } - protected void attachFileDialog() { - View menuAttachFile = findViewById(R.id.action_attach_file); - if (menuAttachFile == null) { - return; - } - PopupMenu attachFilePopup = new PopupMenu(this, menuAttachFile); - attachFilePopup.inflate(R.menu.attachment_choices); - attachFilePopup.setOnMenuItemClickListener(new OnMenuItemClickListener() { - - @Override - public boolean onMenuItemClick(MenuItem item) { - switch (item.getItemId()) { - case R.id.attach_choose_picture: - attachFile(ATTACHMENT_CHOICE_CHOOSE_IMAGE); - break; - case R.id.attach_take_picture: - attachFile(ATTACHMENT_CHOICE_TAKE_FROM_CAMERA); - break; - case R.id.attach_choose_video: - attachFile(ATTACHMENT_CHOICE_CHOOSE_VIDEO); - break; - case R.id.attach_choose_file: - attachFile(ATTACHMENT_CHOICE_CHOOSE_FILE); - break; - case R.id.attach_record_voice: - attachFile(ATTACHMENT_CHOICE_RECORD_VOICE); - break; - case R.id.attach_location: - attachFile(ATTACHMENT_CHOICE_LOCATION); - break; - } - return false; - } - }); - UIHelper.showIconsInPopup(attachFilePopup); - attachFilePopup.show(); - } - public void verifyOtrSessionDialog(final Conversation conversation, View view) { if (!conversation.hasValidOtrSession() || conversation.getOtrSession().getSessionStatus() != SessionStatus.ENCRYPTED) { Toast.makeText(this, R.string.otr_session_not_started, Toast.LENGTH_LONG).show(); @@ -998,91 +951,47 @@ public class ConversationActivity extends XmppActivity popup.show(); } - protected void selectEncryptionDialog(final Conversation conversation) { - View menuItemView = findViewById(R.id.action_security); - if (menuItemView == null) { + private void handleEncryptionSelection(MenuItem item) { + Conversation conversation = getSelectedConversation(); + if (conversation == null) { return; } - PopupMenu popup = new PopupMenu(this, menuItemView); - final ConversationFragment fragment = (ConversationFragment) getFragmentManager() - .findFragmentByTag("conversation"); - if (fragment != null) { - popup.setOnMenuItemClickListener(new OnMenuItemClickListener() { - - @Override - public boolean onMenuItemClick(MenuItem item) { - switch (item.getItemId()) { - case R.id.encryption_choice_none: - conversation.setNextEncryption(Message.ENCRYPTION_NONE); - item.setChecked(true); - break; - case R.id.encryption_choice_otr: - conversation.setNextEncryption(Message.ENCRYPTION_OTR); - item.setChecked(true); - break; - case R.id.encryption_choice_pgp: - if (hasPgp()) { - if (conversation.getAccount().getPgpSignature() != null) { - conversation.setNextEncryption(Message.ENCRYPTION_PGP); - item.setChecked(true); - } else { - announcePgp(conversation.getAccount(), conversation, null, onOpenPGPKeyPublished); - } - } else { - showInstallPgpDialog(); - } - break; - case R.id.encryption_choice_axolotl: - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(conversation.getAccount()) - + "Enabled axolotl for Contact " + conversation.getContact().getJid()); - conversation.setNextEncryption(Message.ENCRYPTION_AXOLOTL); - item.setChecked(true); - break; - default: - conversation.setNextEncryption(Message.ENCRYPTION_NONE); - break; + final ConversationFragment fragment = (ConversationFragment) getFragmentManager().findFragmentByTag("conversation"); + switch (item.getItemId()) { + case R.id.encryption_choice_none: + conversation.setNextEncryption(Message.ENCRYPTION_NONE); + item.setChecked(true); + break; + case R.id.encryption_choice_otr: + conversation.setNextEncryption(Message.ENCRYPTION_OTR); + item.setChecked(true); + break; + case R.id.encryption_choice_pgp: + if (hasPgp()) { + if (conversation.getAccount().getPgpSignature() != null) { + conversation.setNextEncryption(Message.ENCRYPTION_PGP); + item.setChecked(true); + } else { + announcePgp(conversation.getAccount(), conversation, null, onOpenPGPKeyPublished); } - xmppConnectionService.updateConversation(conversation); - fragment.updateChatMsgHint(); - invalidateOptionsMenu(); - refreshUi(); - return true; + } else { + showInstallPgpDialog(); } - }); - popup.inflate(R.menu.encryption_choices); - MenuItem otr = popup.getMenu().findItem(R.id.encryption_choice_otr); - MenuItem none = popup.getMenu().findItem(R.id.encryption_choice_none); - MenuItem pgp = popup.getMenu().findItem(R.id.encryption_choice_pgp); - MenuItem axolotl = popup.getMenu().findItem(R.id.encryption_choice_axolotl); - pgp.setVisible(Config.supportOpenPgp()); - none.setVisible(Config.supportUnencrypted() || conversation.getMode() == Conversation.MODE_MULTI); - otr.setVisible(Config.supportOtr()); - axolotl.setVisible(Config.supportOmemo()); - if (conversation.getMode() == Conversation.MODE_MULTI) { - otr.setVisible(false); - } - if (!conversation.getAccount().getAxolotlService().isConversationAxolotlCapable(conversation)) { - axolotl.setEnabled(false); - } - switch (conversation.getNextEncryption()) { - case Message.ENCRYPTION_NONE: - none.setChecked(true); - break; - case Message.ENCRYPTION_OTR: - otr.setChecked(true); - break; - case Message.ENCRYPTION_PGP: - pgp.setChecked(true); - break; - case Message.ENCRYPTION_AXOLOTL: - axolotl.setChecked(true); - break; - default: - none.setChecked(true); - break; - } - popup.show(); + break; + case R.id.encryption_choice_axolotl: + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(conversation.getAccount()) + + "Enabled axolotl for Contact " + conversation.getContact().getJid()); + conversation.setNextEncryption(Message.ENCRYPTION_AXOLOTL); + item.setChecked(true); + break; + default: + conversation.setNextEncryption(Message.ENCRYPTION_NONE); + break; } + xmppConnectionService.updateConversation(conversation); + fragment.updateChatMsgHint(); + invalidateOptionsMenu(); + refreshUi(); } @Override @@ -1260,7 +1169,6 @@ public class ConversationActivity extends XmppActivity recreate(); } this.mActivityPaused = false; - if (!isConversationsOverviewVisable() || !isConversationsOverviewHideable()) { sendReadMarkerIfNecessary(getSelectedConversation()); } @@ -1699,26 +1607,18 @@ public class ConversationActivity extends XmppActivity AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.battery_optimizations_enabled); builder.setMessage(R.string.battery_optimizations_enabled_dialog); - builder.setPositiveButton(R.string.next, new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); - Uri uri = Uri.parse("package:" + getPackageName()); - intent.setData(uri); - try { - startActivityForResult(intent, REQUEST_BATTERY_OP); - } catch (ActivityNotFoundException e) { - Toast.makeText(ConversationActivity.this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show(); - } + builder.setPositiveButton(R.string.next, (dialog, which) -> { + Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + Uri uri = Uri.parse("package:" + getPackageName()); + intent.setData(uri); + try { + startActivityForResult(intent, REQUEST_BATTERY_OP); + } catch (ActivityNotFoundException e) { + Toast.makeText(ConversationActivity.this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show(); } }); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - builder.setOnDismissListener(new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - setNeverAskForBatteryOptimizationsAgain(); - } - }); + builder.setOnDismissListener(dialog -> setNeverAskForBatteryOptimizationsAgain()); } builder.create().show(); } diff --git a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java index e73f30211..e4c648e25 100644 --- a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java +++ b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java @@ -1,7 +1,7 @@ package de.pixart.messenger.ui; import android.app.Activity; -import android.app.AlertDialog; +import android.support.v7.app.AlertDialog; import android.app.Fragment; import android.app.PendingIntent; import android.content.ActivityNotFoundException; diff --git a/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java b/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java index c94ceb44b..d26c2123c 100644 --- a/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java +++ b/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java @@ -1,27 +1,28 @@ package de.pixart.messenger.ui; -import android.app.ActionBar; -import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.app.PendingIntent; import android.content.ActivityNotFoundException; -import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; +import android.databinding.DataBindingUtil; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; +import android.os.Handler; import android.provider.Settings; import android.security.KeyChain; import android.security.KeyChainAliasCallback; +import android.support.design.widget.TextInputLayout; import android.support.v4.content.ContextCompat; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AlertDialog; import android.text.Editable; import android.text.TextWatcher; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; -import android.widget.AutoCompleteTextView; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; @@ -48,6 +49,7 @@ import de.pixart.messenger.Config; import de.pixart.messenger.R; import de.pixart.messenger.crypto.axolotl.AxolotlService; import de.pixart.messenger.crypto.axolotl.XmppAxolotlSession; +import de.pixart.messenger.databinding.ActivityEditAccountBinding; import de.pixart.messenger.entities.Account; import de.pixart.messenger.services.BarcodeProvider; import de.pixart.messenger.services.XmppConnectionService; @@ -66,24 +68,20 @@ import de.pixart.messenger.xmpp.forms.Data; import de.pixart.messenger.xmpp.jid.InvalidJidException; import de.pixart.messenger.xmpp.jid.Jid; import de.pixart.messenger.xmpp.pep.Avatar; - public class EditAccountActivity extends OmemoActivity implements OnAccountUpdate, OnUpdateBlocklist, OnKeyStatusUpdated, OnCaptchaRequested, KeyChainAliasCallback, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnMamPreferencesFetched { private static final int REQUEST_DATA_SAVER = 0x37af244; - private AutoCompleteTextView mAccountJid; + private TextInputLayout mAccountJidLayout; private EditText mPassword; - private EditText mPasswordConfirm; + private TextInputLayout mPasswordLayout; private CheckBox mRegisterNew; private Button mCancelButton; private Button mSaveButton; private Button mDisableOsOptimizationsButton; - private TextView mDisableOsOptimizationsHeadline; private TextView getmDisableOsOptimizationsBody; private TableLayout mMoreTable; - private LinearLayout mStats; - private RelativeLayout mOsOptimizations; private TextView mServerInfoSm; private TextView mServerInfoRosterVersion; private TextView mServerInfoCarbons; @@ -100,7 +98,6 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat private TextView mOwnFingerprintDesc; private TextView mOtrFingerprintDesc; private TextView getmPgpFingerprintDesc; - private TextView mAccountJidLabel; private ImageView mAvatar; private RelativeLayout mOtrFingerprintBox; private RelativeLayout mAxolotlFingerprintBox; @@ -109,10 +106,11 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat private ImageButton mAxolotlFingerprintToClipboardButton; private ImageButton mPgpDeleteFingerprintButton; private LinearLayout keys; - private LinearLayout keysCard; private LinearLayout mNamePort; private EditText mHostname; + private TextInputLayout mHostnameLayout; private EditText mPort; + private TextInputLayout mPortLayout; private AlertDialog mCaptchaDialog = null; private Jid jidToEdit; @@ -129,7 +127,6 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat @Override public void onClick(final View v) { final String password = mPassword.getText().toString(); - final String passwordConfirm = mPasswordConfirm.getText().toString(); final boolean wasDisabled = mAccount != null && mAccount.getStatus() == Account.State.DISABLED; if (!mInitMode && passwordChangedInMagicCreateMode()) { @@ -147,9 +144,10 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat return; } final boolean registerNewAccount = mRegisterNew.isChecked() && !Config.DISALLOW_REGISTRATION_IN_UI; - if (mUsernameMode && mAccountJid.getText().toString().contains("@")) { - mAccountJid.setError(getString(R.string.invalid_username)); - mAccountJid.requestFocus(); + if (mUsernameMode && binding.accountJid.getText().toString().contains("@")) { + mAccountJidLayout.setError(getString(R.string.invalid_username)); + removeErrorsOnAllBut(mAccountJidLayout); + binding.accountJid.requestFocus(); return; } @@ -171,17 +169,18 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat final Jid jid; try { if (mUsernameMode) { - jid = Jid.fromParts(mAccountJid.getText().toString(), getUserModeDomain(), null); + jid = Jid.fromParts(binding.accountJid.getText().toString(), getUserModeDomain(), null); } else { - jid = Jid.fromString(mAccountJid.getText().toString()); + jid = Jid.fromString(binding.accountJid.getText().toString()); } } catch (final InvalidJidException e) { if (mUsernameMode) { - mAccountJid.setError(getString(R.string.invalid_username)); + mAccountJidLayout.setError(getString(R.string.invalid_username)); } else { - mAccountJid.setError(getString(R.string.invalid_jid)); + mAccountJidLayout.setError(getString(R.string.invalid_jid)); } - mAccountJid.requestFocus(); + binding.accountJid.requestFocus(); + removeErrorsOnAllBut(mAccountJidLayout); return; } String hostname = null; @@ -190,20 +189,23 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat hostname = mHostname.getText().toString().replaceAll("\\s", ""); final String port = mPort.getText().toString().replaceAll("\\s", ""); if (hostname.contains(" ")) { - mHostname.setError(getString(R.string.not_valid_hostname)); + mHostnameLayout.setError(getString(R.string.not_valid_hostname)); mHostname.requestFocus(); + removeErrorsOnAllBut(mHostnameLayout); return; } try { numericPort = Integer.parseInt(port); if (numericPort < 0 || numericPort > 65535) { - mPort.setError(getString(R.string.not_a_valid_port)); + mPortLayout.setError(getString(R.string.not_a_valid_port)); + removeErrorsOnAllBut(mPortLayout); mPort.requestFocus(); return; } } catch (NumberFormatException e) { - mPort.setError(getString(R.string.not_a_valid_port)); + mPortLayout.setError(getString(R.string.not_a_valid_port)); + removeErrorsOnAllBut(mPortLayout); mPort.requestFocus(); return; } @@ -211,22 +213,18 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat if (jid.isDomainJid()) { if (mUsernameMode) { - mAccountJid.setError(getString(R.string.invalid_username)); + mAccountJidLayout.setError(getString(R.string.invalid_username)); } else { - mAccountJid.setError(getString(R.string.invalid_jid)); + mAccountJidLayout.setError(getString(R.string.invalid_jid)); } - mAccountJid.requestFocus(); + removeErrorsOnAllBut(mAccountJidLayout); + binding.accountJid.requestFocus(); return; } if (registerNewAccount) { if (XmppConnection.errorMessage != null) { Toast.makeText(EditAccountActivity.this, XmppConnection.errorMessage, Toast.LENGTH_LONG).show(); } - if (!password.equals(passwordConfirm)) { - mPasswordConfirm.setError(getString(R.string.passwords_do_not_match)); - mPasswordConfirm.requestFocus(); - return; - } } if (mAccount != null) { if (mInitMode && mAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) { @@ -236,11 +234,10 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat mAccount.setPort(numericPort); mAccount.setHostname(hostname); if (XmppConnection.errorMessage != null) { - mAccountJid.setError(XmppConnection.errorMessage); + mAccountJidLayout.setError(XmppConnection.errorMessage); } else { - mAccountJid.setError(null); + mAccountJidLayout.setError(null); } - mPasswordConfirm.setError(null); mAccount.setPassword(password); mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount); if (!xmppConnectionService.updateAccount(mAccount)) { @@ -249,8 +246,9 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } } else { if (xmppConnectionService.findAccountByJid(jid) != null) { - mAccountJid.setError(getString(R.string.account_already_exists)); - mAccountJid.requestFocus(); + mAccountJidLayout.setError(getString(R.string.account_already_exists)); + removeErrorsOnAllBut(mAccountJidLayout); + binding.accountJid.requestFocus(); return; } mAccount = new Account(jid.toBareJid(), password); @@ -261,8 +259,8 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat mAccount.setOption(Account.OPTION_REGISTER, registerNewAccount); xmppConnectionService.createAccount(mAccount); } - mHostname.setError(null); - mPort.setError(null); + mHostnameLayout.setError(null); + mPortLayout.setError(null); if (mAccount.isEnabled() && !registerNewAccount && !mInitMode) { @@ -288,6 +286,8 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat private boolean mSavedInstanceInit = false; private Button mClearDevicesButton; private XmppUri pendingUri = null; + private boolean mUseTor; + private ActivityEditAccountBinding binding; public void refreshUiReal() { invalidateOptionsMenu(); @@ -371,6 +371,24 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } }; + private View.OnFocusChangeListener mEditTextFocusListener = new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View view, boolean b) { + EditText et = (EditText) view; + if (b) { + int resId = mUsernameMode ? R.string.username : R.string.account_settings_example_jabber_id; + if (view.getId() == R.id.hostname) { + resId = mUseTor ? R.string.hostname_or_onion : R.string.hostname_example; + } + final int res = resId; + new Handler().postDelayed(() -> et.setHint(res), 500); + } else { + et.setHint(null); + } + } + }; + + private final OnClickListener mAvatarClickListener = new OnClickListener() { @Override public void onClick(final View view) { @@ -499,7 +517,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } else { unmodified = this.mAccount.getJid().toBareJid().toString(); } - return !unmodified.equals(this.mAccountJid.getText().toString()); + return !unmodified.equals(this.binding.accountJid.getText().toString()); } protected boolean passwordChangedInMagicCreateMode() { @@ -526,20 +544,17 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat this.mSavedInstanceAccount = savedInstanceState.getString("account"); this.mSavedInstanceInit = savedInstanceState.getBoolean("initMode", false); } - setContentView(R.layout.activity_edit_account); - this.mAccountJid = findViewById(R.id.account_jid); - this.mAccountJid.addTextChangedListener(this.mTextWatcher); - this.mAccountJidLabel = findViewById(R.id.account_jid_label); + this.binding = DataBindingUtil.setContentView(this, R.layout.activity_edit_account); + this.binding.accountJid.addTextChangedListener(this.mTextWatcher); + this.binding.accountJid.setOnFocusChangeListener(this.mEditTextFocusListener); + this.mAccountJidLayout = (TextInputLayout) findViewById(R.id.account_jid_layout); this.mPassword = findViewById(R.id.account_password); this.mPassword.addTextChangedListener(this.mTextWatcher); - this.mPasswordConfirm = findViewById(R.id.account_password_confirm); + this.mPasswordLayout = (TextInputLayout) findViewById(R.id.account_password_layout); this.mAvatar = findViewById(R.id.avater); this.mAvatar.setOnClickListener(this.mAvatarClickListener); this.mRegisterNew = findViewById(R.id.account_register_new); - this.mStats = findViewById(R.id.stats); - this.mOsOptimizations = findViewById(R.id.os_optimization); this.mDisableOsOptimizationsButton = findViewById(R.id.os_optimization_disable); - this.mDisableOsOptimizationsHeadline = findViewById(R.id.os_optimization_headline); this.getmDisableOsOptimizationsBody = findViewById(R.id.os_optimization_body); this.mSessionEst = findViewById(R.id.session_est); this.mServerInfoRosterVersion = findViewById(R.id.server_info_roster_version); @@ -564,11 +579,12 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat this.mAxolotlFingerprintBox = findViewById(R.id.axolotl_fingerprint_box); this.mAxolotlFingerprintToClipboardButton = findViewById(R.id.action_copy_axolotl_to_clipboard); this.mOwnFingerprintDesc = findViewById(R.id.own_fingerprint_desc); - this.keysCard = findViewById(R.id.other_device_keys_card); this.keys = findViewById(R.id.other_device_keys); this.mNamePort = findViewById(R.id.name_port); this.mHostname = findViewById(R.id.hostname); this.mHostname.addTextChangedListener(mTextWatcher); + this.mHostname.setOnFocusChangeListener(mEditTextFocusListener); + this.mHostnameLayout = (TextInputLayout)findViewById(R.id.hostname_layout); this.mClearDevicesButton = findViewById(R.id.clear_devices); this.mClearDevicesButton.setOnClickListener(new OnClickListener() { @Override @@ -579,6 +595,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat this.mPort = findViewById(R.id.port); this.mPort.setText("5222"); this.mPort.addTextChangedListener(mTextWatcher); + this.mPortLayout = (TextInputLayout)findViewById(R.id.port_layout); this.mSaveButton = findViewById(R.id.save_button); this.mCancelButton = findViewById(R.id.cancel_button); this.mSaveButton.setOnClickListener(this.mSaveButtonClickListener); @@ -589,13 +606,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } final OnCheckedChangeListener OnCheckedShowConfirmPassword = new OnCheckedChangeListener() { @Override - public void onCheckedChanged(final CompoundButton buttonView, - final boolean isChecked) { - if (isChecked) { - mPasswordConfirm.setVisibility(View.VISIBLE); - } else { - mPasswordConfirm.setVisibility(View.GONE); - } + public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) { updateSaveButton(); } }; @@ -688,12 +699,12 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat this.messageFingerprint = getIntent().getStringExtra("fingerprint"); if (!mInitMode) { this.mRegisterNew.setVisibility(View.GONE); - if (getActionBar() != null) { - getActionBar().setTitle(getString(R.string.account_details)); + if (getSupportActionBar() != null) { + getSupportActionBar().setTitle(getString(R.string.account_details)); } } else { this.mAvatar.setVisibility(View.GONE); - ActionBar ab = getActionBar(); + ActionBar ab = getSupportActionBar(); if (ab != null) { if (init && Config.MAGIC_CREATE_DOMAIN == null) { ab.setDisplayShowHomeEnabled(false); @@ -704,9 +715,8 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } } SharedPreferences preferences = getPreferences(); - boolean useTor = Config.FORCE_ORBOT || preferences.getBoolean("use_tor", false); - this.mShowOptions = useTor || preferences.getBoolean("show_connection_options", false); - mHostname.setHint(useTor ? R.string.hostname_or_onion : R.string.hostname_example); + mUseTor = Config.FORCE_ORBOT || preferences.getBoolean("use_tor", false); + this.mShowOptions = mUseTor || preferences.getBoolean("show_connection_options", false); this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE); } @@ -769,13 +779,12 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat this.mCancelButton.setTextColor(getSecondaryTextColor()); } if (mUsernameMode) { - this.mAccountJidLabel.setText(R.string.username); - this.mAccountJid.setHint(R.string.username_hint); + this.binding.accountJid.setHint(R.string.username_hint); } else { final KnownHostsAdapter mKnownHostsAdapter = new KnownHostsAdapter(this, R.layout.simple_list_item, xmppConnectionService.getKnownHosts()); - this.mAccountJid.setAdapter(mKnownHostsAdapter); + this.binding.accountJid.setAdapter(mKnownHostsAdapter); } if (pendingUri != null) { processFingerprintVerification(pendingUri, false); @@ -890,11 +899,11 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat private void updateAccountInformation(boolean init) { if (init) { - this.mAccountJid.getEditableText().clear(); + this.binding.accountJid.getEditableText().clear(); if (mUsernameMode) { - this.mAccountJid.getEditableText().append(this.mAccount.getJid().getLocalpart()); + this.binding.accountJid.getEditableText().append(this.mAccount.getJid().getLocalpart()); } else { - this.mAccountJid.getEditableText().append(this.mAccount.getJid().toBareJid().toString()); + this.binding.accountJid.getEditableText().append(this.mAccount.getJid().toBareJid().toString()); } this.mPassword.getEditableText().clear(); this.mPassword.getEditableText().append(this.mAccount.getPassword()); @@ -908,28 +917,28 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } final boolean editable = !mAccount.isOptionSet(Account.OPTION_LOGGED_IN_SUCCESSFULLY); - this.mAccountJid.setEnabled(editable); - this.mAccountJid.setFocusable(editable); - this.mAccountJid.setFocusableInTouchMode(editable); + this.binding.accountJid.setEnabled(editable); + this.binding.accountJid.setFocusable(editable); + this.binding.accountJid.setFocusableInTouchMode(editable); + this.mPasswordLayout.setPasswordVisibilityToggleEnabled(editable); if (!mInitMode) { this.mAvatar.setVisibility(View.VISIBLE); this.mAvatar.setImageBitmap(avatarService().get(this.mAccount, getPixel(Config.AVATAR_SIZE))); - this.mAccountJid.setEnabled(false); + this.binding.accountJid.setEnabled(false); } else { this.mAvatar.setVisibility(View.GONE); } if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) { this.mRegisterNew.setVisibility(View.VISIBLE); this.mRegisterNew.setChecked(true); - this.mPasswordConfirm.setText(this.mAccount.getPassword()); } else { this.mRegisterNew.setVisibility(View.GONE); this.mRegisterNew.setChecked(false); } if (this.mAccount.isOnlineAndConnected() && !this.mFetchingAvatar) { Features features = this.mAccount.getXmppConnection().getFeatures(); - this.mStats.setVisibility(View.VISIBLE); + this.binding.stats.setVisibility(View.VISIBLE); boolean showBatteryWarning = !xmppConnectionService.getPushManagementService().available(mAccount) && isOptimizingBattery(); boolean showDataSaverWarning = isAffectedByDataSaver(); showOsOptimizationWarning(showBatteryWarning, showDataSaverWarning); @@ -993,18 +1002,8 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } final long pgpKeyId = this.mAccount.getPgpId(); if (pgpKeyId != 0 && Config.supportOpenPgp()) { - OnClickListener openPgp = new OnClickListener() { - @Override - public void onClick(View view) { - launchOpenKeyChain(pgpKeyId); - } - }; - OnClickListener delete = new OnClickListener() { - @Override - public void onClick(View view) { - showDeletePgpDialog(); - } - }; + OnClickListener openPgp = view -> launchOpenKeyChain(pgpKeyId); + OnClickListener delete = view -> showDeletePgpDialog(); this.mPgpFingerprintBox.setVisibility(View.VISIBLE); this.mPgpFingerprint.setText(OpenPgpUtils.convertKeyIdToHex(pgpKeyId)); this.mPgpFingerprint.setOnClickListener(openPgp); @@ -1023,20 +1022,13 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } this.mOtrFingerprintBox.setVisibility(View.VISIBLE); this.mOtrFingerprint.setText(CryptoHelper.prettifyFingerprint(otrFingerprint)); - this.mOtrFingerprintToClipboardButton - .setVisibility(View.VISIBLE); - this.mOtrFingerprintToClipboardButton - .setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(final View v) { - - if (copyTextToClipboard(CryptoHelper.prettifyFingerprint(otrFingerprint), R.string.otr_fingerprint)) { - Toast.makeText( - EditAccountActivity.this, - R.string.toast_message_otr_fingerprint, - Toast.LENGTH_SHORT).show(); - } + this.mOtrFingerprintToClipboardButton.setVisibility(View.VISIBLE); + this.mOtrFingerprintToClipboardButton.setOnClickListener(v -> { + if (copyTextToClipboard(CryptoHelper.prettifyFingerprint(otrFingerprint), R.string.otr_fingerprint)) { + Toast.makeText( + EditAccountActivity.this, + R.string.toast_message_otr_fingerprint, + Toast.LENGTH_SHORT).show(); } }); } else { @@ -1053,16 +1045,8 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat this.mOwnFingerprintDesc.setText(R.string.omemo_fingerprint); } this.mAxolotlFingerprint.setText(CryptoHelper.prettifyFingerprint(ownAxolotlFingerprint.substring(2))); - this.mAxolotlFingerprintToClipboardButton - .setVisibility(View.VISIBLE); - this.mAxolotlFingerprintToClipboardButton - .setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(final View v) { - copyOmemoFingerprint(ownAxolotlFingerprint); - } - }); + this.mAxolotlFingerprintToClipboardButton.setVisibility(View.VISIBLE); + this.mAxolotlFingerprintToClipboardButton.setOnClickListener(v -> copyOmemoFingerprint(ownAxolotlFingerprint)); } else { this.mAxolotlFingerprintBox.setVisibility(View.GONE); } @@ -1076,7 +1060,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } } if (hasKeys && Config.supportOmemo()) { - keysCard.setVisibility(View.VISIBLE); + this.binding.otherDeviceKeysCard.setVisibility(View.VISIBLE); Set<Integer> otherDevices = mAccount.getAxolotlService().getOwnDeviceIds(); if (otherDevices == null || otherDevices.isEmpty()) { mClearDevicesButton.setVisibility(View.GONE); @@ -1084,30 +1068,49 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat mClearDevicesButton.setVisibility(View.VISIBLE); } } else { - keysCard.setVisibility(View.GONE); + this.binding.otherDeviceKeysCard.setVisibility(View.GONE); } } else { + final TextInputLayout errorLayout; if (this.mAccount.errorStatus()) { - final EditText errorTextField; if (this.mAccount.getStatus() == Account.State.UNAUTHORIZED) { - errorTextField = this.mPassword; + errorLayout = this.mPasswordLayout; } else if (mShowOptions && this.mAccount.getStatus() == Account.State.SERVER_NOT_FOUND && this.mHostname.getText().length() > 0) { - errorTextField = this.mHostname; + errorLayout = this.mHostnameLayout; } else { - errorTextField = this.mAccountJid; + errorLayout = this.mAccountJidLayout; } - errorTextField.setError(getString(this.mAccount.getStatus().getReadableId())); + errorLayout.setError(getString(this.mAccount.getStatus().getReadableId())); if (init || !accountInfoEdited()) { - errorTextField.requestFocus(); + errorLayout.requestFocus(); } } else { - this.mAccountJid.setError(null); - this.mPassword.setError(null); - this.mHostname.setError(null); + errorLayout = null; } - this.mStats.setVisibility(View.GONE); + removeErrorsOnAllBut(errorLayout); + this.binding.stats.setVisibility(View.GONE); + this.binding.otherDeviceKeysCard.setVisibility(View.GONE); + } + } + + private void removeErrorsOnAllBut(TextInputLayout exception) { + if (this.mAccountJidLayout != exception) { + this.mAccountJidLayout.setErrorEnabled(false); + this.mAccountJidLayout.setError(null); + } + if (this.mPasswordLayout != exception) { + this.mPasswordLayout.setErrorEnabled(false); + this.mPasswordLayout.setError(null); + } + if (this.mHostnameLayout != exception) { + this.mHostnameLayout.setErrorEnabled(false); + this.mHostnameLayout.setError(null); + } + if (this.mPortLayout != exception) { + this.mPortLayout.setErrorEnabled(false); + this.mPortLayout.setError(null); } } @@ -1116,53 +1119,44 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat builder.setTitle(R.string.unpublish_pgp); builder.setMessage(R.string.unpublish_pgp_message); builder.setNegativeButton(R.string.cancel, null); - builder.setPositiveButton(R.string.confirm, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - mAccount.setPgpSignId(0); - mAccount.unsetPgpSignature(); - xmppConnectionService.databaseBackend.updateAccount(mAccount); - xmppConnectionService.sendPresence(mAccount); - refreshUiReal(); - } + builder.setPositiveButton(R.string.confirm, (dialogInterface, i) -> { + mAccount.setPgpSignId(0); + mAccount.unsetPgpSignature(); + xmppConnectionService.databaseBackend.updateAccount(mAccount); + xmppConnectionService.sendPresence(mAccount); + refreshUiReal(); }); builder.create().show(); } private void showOsOptimizationWarning(boolean showBatteryWarning, boolean showDataSaverWarning) { - this.mOsOptimizations.setVisibility(showBatteryWarning || showDataSaverWarning ? View.VISIBLE : View.GONE); + this.binding.osOptimization.setVisibility(showBatteryWarning || showDataSaverWarning ? View.VISIBLE : View.GONE); if (showDataSaverWarning) { - this.mDisableOsOptimizationsHeadline.setText(R.string.data_saver_enabled); + this.binding.osOptimizationHeadline.setText(R.string.data_saver_enabled); this.getmDisableOsOptimizationsBody.setText(R.string.data_saver_enabled_explained); this.mDisableOsOptimizationsButton.setText(R.string.allow); - this.mDisableOsOptimizationsButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(Settings.ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS); - Uri uri = Uri.parse("package:" + getPackageName()); - intent.setData(uri); - try { - startActivityForResult(intent, REQUEST_DATA_SAVER); - } catch (ActivityNotFoundException e) { - Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_data_saver, Toast.LENGTH_SHORT).show(); - } + this.mDisableOsOptimizationsButton.setOnClickListener(v -> { + Intent intent = new Intent(Settings.ACTION_IGNORE_BACKGROUND_DATA_RESTRICTIONS_SETTINGS); + Uri uri = Uri.parse("package:" + getPackageName()); + intent.setData(uri); + try { + startActivityForResult(intent, REQUEST_DATA_SAVER); + } catch (ActivityNotFoundException e) { + Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_data_saver, Toast.LENGTH_SHORT).show(); } }); } else if (showBatteryWarning) { this.mDisableOsOptimizationsButton.setText(R.string.disable); - this.mDisableOsOptimizationsHeadline.setText(R.string.battery_optimizations_enabled); + this.binding.osOptimizationHeadline.setText(R.string.battery_optimizations_enabled); this.getmDisableOsOptimizationsBody.setText(R.string.battery_optimizations_enabled_explained); - this.mDisableOsOptimizationsButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); - Uri uri = Uri.parse("package:" + getPackageName()); - intent.setData(uri); - try { - startActivityForResult(intent, REQUEST_BATTERY_OP); - } catch (ActivityNotFoundException e) { - Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show(); - } + this.mDisableOsOptimizationsButton.setOnClickListener(v -> { + Intent intent = new Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS); + Uri uri = Uri.parse("package:" + getPackageName()); + intent.setData(uri); + try { + startActivityForResult(intent, REQUEST_BATTERY_OP); + } catch (ActivityNotFoundException e) { + Toast.makeText(EditAccountActivity.this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show(); } }); } @@ -1175,12 +1169,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat builder.setMessage(getString(R.string.clear_other_devices_desc)); builder.setNegativeButton(getString(R.string.cancel), null); builder.setPositiveButton(getString(R.string.accept), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - mAccount.getAxolotlService().wipeOtherPepDevices(); - } - }); + (dialog, which) -> mAccount.getAxolotlService().wipeOtherPepDevices()); builder.create().show(); } @@ -1208,58 +1197,46 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat @Override public void onCaptchaRequested(final Account account, final String id, final Data data, final Bitmap captcha) { - runOnUiThread(new Runnable() { - @Override - public void run() { - if ((mCaptchaDialog != null) && mCaptchaDialog.isShowing()) { - mCaptchaDialog.dismiss(); - } - final AlertDialog.Builder builder = new AlertDialog.Builder(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); - imageView.setImageBitmap(captcha); - - builder.setTitle(getString(R.string.captcha_required)); - builder.setView(view); - - builder.setPositiveButton(getString(R.string.ok), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - String rc = input.getText().toString(); - data.put("username", account.getUsername()); - data.put("password", account.getPassword()); - data.put("ocr", rc); - data.submit(); - - if (xmppConnectionServiceBound) { - xmppConnectionService.sendCreateAccountWithCaptchaPacket( - account, id, data); - } - } - }); - builder.setNegativeButton(getString(R.string.cancel), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - if (xmppConnectionService != null) { - xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null); + runOnUiThread(() -> { + if ((mCaptchaDialog != null) && mCaptchaDialog.isShowing()) { + mCaptchaDialog.dismiss(); + } + final AlertDialog.Builder builder = new AlertDialog.Builder(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); + imageView.setImageBitmap(captcha); + + builder.setTitle(getString(R.string.captcha_required)); + builder.setView(view); + + builder.setPositiveButton(getString(R.string.ok), + (dialog, which) -> { + String rc = input.getText().toString(); + data.put("username", account.getUsername()); + data.put("password", account.getPassword()); + data.put("ocr", rc); + data.submit(); + + if (xmppConnectionServiceBound) { + xmppConnectionService.sendCreateAccountWithCaptchaPacket( + account, id, data); } - } - }); + }); + builder.setNegativeButton(getString(R.string.cancel), (dialog, which) -> { + if (xmppConnectionService != null) { + xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null); + } + }); - builder.setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - if (xmppConnectionService != null) { - xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null); - } - } - }); - mCaptchaDialog = builder.create(); - mCaptchaDialog.show(); - input.requestFocus(); - } + builder.setOnCancelListener(dialog -> { + if (xmppConnectionService != null) { + xmppConnectionService.sendCreateAccountWithCaptchaPacket(account, null, null); + } + }); + mCaptchaDialog = builder.create(); + mCaptchaDialog.show(); + input.requestFocus(); }); } @@ -1274,46 +1251,32 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat @Override public void onPreferencesFetched(final Element prefs) { - runOnUiThread(new Runnable() { - @Override - public void run() { - if (mFetchingMamPrefsToast != null) { - mFetchingMamPrefsToast.cancel(); - } - AlertDialog.Builder builder = new Builder(EditAccountActivity.this); - builder.setTitle(R.string.server_side_mam_prefs); - String defaultAttr = prefs.getAttribute("default"); - final List<String> defaults = Arrays.asList("never", "roster", "always"); - final AtomicInteger choice = new AtomicInteger(Math.max(0, defaults.indexOf(defaultAttr))); - builder.setSingleChoiceItems(R.array.mam_prefs, choice.get(), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - choice.set(which); - } - }); - builder.setNegativeButton(R.string.cancel, null); - builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - prefs.setAttribute("default", defaults.get(choice.get())); - xmppConnectionService.pushMamPreferences(mAccount, prefs); - } - }); - builder.create().show(); + runOnUiThread(() -> { + if (mFetchingMamPrefsToast != null) { + mFetchingMamPrefsToast.cancel(); } + Builder builder = new Builder(EditAccountActivity.this); + builder.setTitle(R.string.server_side_mam_prefs); + String defaultAttr = prefs.getAttribute("default"); + final List<String> defaults = Arrays.asList("never", "roster", "always"); + final AtomicInteger choice = new AtomicInteger(Math.max(0, defaults.indexOf(defaultAttr))); + builder.setSingleChoiceItems(R.array.mam_prefs, choice.get(), (dialog, which) -> choice.set(which)); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.ok, (dialog, which) -> { + prefs.setAttribute("default", defaults.get(choice.get())); + xmppConnectionService.pushMamPreferences(mAccount, prefs); + }); + builder.create().show(); }); } @Override public void onPreferencesFetchFailed() { - runOnUiThread(new Runnable() { - @Override - public void run() { - if (mFetchingMamPrefsToast != null) { - mFetchingMamPrefsToast.cancel(); - } - Toast.makeText(EditAccountActivity.this, R.string.unable_to_fetch_mam_prefs, Toast.LENGTH_LONG).show(); + runOnUiThread(() -> { + if (mFetchingMamPrefsToast != null) { + mFetchingMamPrefsToast.cancel(); } + Toast.makeText(EditAccountActivity.this, R.string.unable_to_fetch_mam_prefs, Toast.LENGTH_LONG).show(); }); } diff --git a/src/main/java/de/pixart/messenger/ui/EnterJidDialog.java b/src/main/java/de/pixart/messenger/ui/EnterJidDialog.java index 4fabd3e89..d43c34ee4 100644 --- a/src/main/java/de/pixart/messenger/ui/EnterJidDialog.java +++ b/src/main/java/de/pixart/messenger/ui/EnterJidDialog.java @@ -1,6 +1,6 @@ package de.pixart.messenger.ui; -import android.app.AlertDialog; +import android.support.v7.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.view.LayoutInflater; diff --git a/src/main/java/de/pixart/messenger/ui/LocationActivity.java b/src/main/java/de/pixart/messenger/ui/LocationActivity.java index 6389fa7d4..04bd03584 100644 --- a/src/main/java/de/pixart/messenger/ui/LocationActivity.java +++ b/src/main/java/de/pixart/messenger/ui/LocationActivity.java @@ -6,10 +6,11 @@ import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; import de.pixart.messenger.Config; -public abstract class LocationActivity extends Activity implements LocationListener { +public abstract class LocationActivity extends AppCompatActivity implements LocationListener { private LocationManager locationManager; @Override diff --git a/src/main/java/de/pixart/messenger/ui/ManageAccountActivity.java b/src/main/java/de/pixart/messenger/ui/ManageAccountActivity.java index 2d0d98ce7..b98efde3b 100644 --- a/src/main/java/de/pixart/messenger/ui/ManageAccountActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ManageAccountActivity.java @@ -1,12 +1,12 @@ package de.pixart.messenger.ui; -import android.app.ActionBar; -import android.app.AlertDialog; +import android.support.v7.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.Intent; import android.os.Bundle; import android.security.KeyChain; import android.security.KeyChainAliasCallback; +import android.support.v7.app.ActionBar; import android.util.Pair; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; @@ -60,7 +60,7 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda accountList.clear(); accountList.addAll(xmppConnectionService.getAccounts()); } - ActionBar actionBar = getActionBar(); + ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setHomeButtonEnabled(this.accountList.size() > 0); actionBar.setDisplayHomeAsUpEnabled(this.accountList.size() > 0); diff --git a/src/main/java/de/pixart/messenger/ui/MemorizingActivity.java b/src/main/java/de/pixart/messenger/ui/MemorizingActivity.java new file mode 100644 index 000000000..8172e6f9c --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/MemorizingActivity.java @@ -0,0 +1,119 @@ +/* MemorizingTrustManager - a TrustManager which asks the user about invalid + * certificates and memorizes their decision. + * + * Copyright (c) 2010 Georg Lukas <georg@op-co.de> + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package de.pixart.messenger.ui; + +import android.content.DialogInterface; +import android.content.DialogInterface.OnCancelListener; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.support.v7.app.AlertDialog; +import android.support.v7.app.AppCompatActivity; + +import java.util.logging.Level; +import java.util.logging.Logger; + +import de.pixart.messenger.R; +import de.pixart.messenger.entities.MTMDecision; +import de.pixart.messenger.services.MemorizingTrustManager; + +public class MemorizingActivity extends AppCompatActivity implements OnClickListener,OnCancelListener { + + private final static Logger LOGGER = Logger.getLogger(MemorizingActivity.class.getName()); + + int decisionId; + + AlertDialog dialog; + + @Override + public void onCreate(Bundle savedInstanceState) { + LOGGER.log(Level.FINE, "onCreate"); + //setTheme(findTheme()); + super.onCreate(savedInstanceState); + } + + @Override + public void onResume() { + super.onResume(); + Intent i = getIntent(); + decisionId = i.getIntExtra(MemorizingTrustManager.DECISION_INTENT_ID, MTMDecision.DECISION_INVALID); + int titleId = i.getIntExtra(MemorizingTrustManager.DECISION_TITLE_ID, R.string.mtm_accept_cert); + String cert = i.getStringExtra(MemorizingTrustManager.DECISION_INTENT_CERT); + LOGGER.log(Level.FINE, "onResume with " + i.getExtras() + " decId=" + decisionId + " data: " + i.getData()); + dialog = new AlertDialog.Builder(this).setTitle(titleId) + .setMessage(cert) + .setPositiveButton(R.string.always, this) + .setNeutralButton(R.string.once, this) + .setNegativeButton(R.string.cancel, this) + .setOnCancelListener(this) + .create(); + dialog.show(); + } + + @Override + protected void onPause() { + if (dialog.isShowing()) + dialog.dismiss(); + super.onPause(); + } + + void sendDecision(int decision) { + LOGGER.log(Level.FINE, "Sending decision: " + decision); + MemorizingTrustManager.interactResult(decisionId, decision); + finish(); + } + + protected int findTheme() { + return 0; + //return getPreferences().getString(SettingsActivity.THEME, getResources().getString(R.string.theme)).equals("dark") ? R.style.ConversationsTheme_Dark : R.style.ConversationsTheme; + } + + protected SharedPreferences getPreferences() { + return PreferenceManager + .getDefaultSharedPreferences(getApplicationContext()); + } + + // react on AlertDialog button press + public void onClick(DialogInterface dialog, int btnId) { + int decision; + dialog.dismiss(); + switch (btnId) { + case DialogInterface.BUTTON_POSITIVE: + decision = MTMDecision.DECISION_ALWAYS; + break; + case DialogInterface.BUTTON_NEUTRAL: + decision = MTMDecision.DECISION_ONCE; + break; + default: + decision = MTMDecision.DECISION_ABORT; + } + sendDecision(decision); + } + + public void onCancel(DialogInterface dialog) { + sendDecision(MTMDecision.DECISION_ABORT); + } +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/OmemoActivity.java b/src/main/java/de/pixart/messenger/ui/OmemoActivity.java index f45134e34..a549c76e3 100644 --- a/src/main/java/de/pixart/messenger/ui/OmemoActivity.java +++ b/src/main/java/de/pixart/messenger/ui/OmemoActivity.java @@ -1,16 +1,15 @@ package de.pixart.messenger.ui; -import android.app.AlertDialog; -import android.content.DialogInterface; import android.content.Intent; +import android.databinding.DataBindingUtil; import android.os.Bundle; import android.support.v4.content.ContextCompat; +import android.support.v7.app.AlertDialog; import android.view.ContextMenu; import android.view.MenuItem; import android.view.View; import android.widget.CompoundButton; -import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; @@ -22,8 +21,8 @@ import de.pixart.messenger.Config; import de.pixart.messenger.R; import de.pixart.messenger.crypto.axolotl.FingerprintStatus; import de.pixart.messenger.crypto.axolotl.XmppAxolotlSession; +import de.pixart.messenger.databinding.ContactKeyBinding; import de.pixart.messenger.entities.Account; -import de.pixart.messenger.ui.widget.Switch; import de.pixart.messenger.utils.CryptoHelper; import de.pixart.messenger.utils.XmppUri; import de.pixart.messenger.utils.zxing.IntentIntegrator; @@ -116,12 +115,7 @@ public abstract class OmemoActivity extends XmppActivity { session.getTrust(), true, true, - new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - account.getAxolotlService().setFingerprintTrust(fingerprint, FingerprintStatus.createActive(isChecked)); - } - }); + (buttonView, isChecked) -> account.getAxolotlService().setFingerprintTrust(fingerprint, FingerprintStatus.createActive(isChecked))); } protected void addFingerprintRowWithListeners(LinearLayout keys, final Account account, @@ -133,110 +127,81 @@ public abstract class OmemoActivity extends XmppActivity { CompoundButton.OnCheckedChangeListener onCheckedChangeListener) { - View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false); - TextView key = view.findViewById(R.id.key); - TextView keyType = view.findViewById(R.id.key_type); + ContactKeyBinding binding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.contact_key, keys, true); if (Config.X509_VERIFICATION && status.getTrust() == FingerprintStatus.Trust.VERIFIED_X509) { - View.OnClickListener listener = new View.OnClickListener() { - @Override - public void onClick(View v) { - showX509Certificate(account, fingerprint); - } - }; - key.setOnClickListener(listener); - keyType.setOnClickListener(listener); + binding.key.setOnClickListener(v -> showX509Certificate(account, fingerprint)); + binding.keyType.setOnClickListener(v -> showX509Certificate(account, fingerprint)); } - Switch trustToggle = view.findViewById(R.id.tgl_trust); - ImageView verifiedFingerprintSymbol = view.findViewById(R.id.verified_fingerprint); - trustToggle.setVisibility(View.VISIBLE); - registerForContextMenu(view); - view.setTag(R.id.TAG_ACCOUNT, account); - view.setTag(R.id.TAG_FINGERPRINT, fingerprint); - view.setTag(R.id.TAG_FINGERPRINT_STATUS, status); + binding.tglTrust.setVisibility(View.VISIBLE); + registerForContextMenu(binding.getRoot()); + binding.getRoot().setTag(R.id.TAG_ACCOUNT, account); + binding.getRoot().setTag(R.id.TAG_FINGERPRINT, fingerprint); + binding.getRoot().setTag(R.id.TAG_FINGERPRINT_STATUS, status); boolean x509 = Config.X509_VERIFICATION && status.getTrust() == FingerprintStatus.Trust.VERIFIED_X509; final View.OnClickListener toast; - trustToggle.setChecked(status.isTrusted(), false); + binding.tglTrust.setChecked(status.isTrusted()); if (status.isActive()) { - key.setTextColor(getPrimaryTextColor()); - keyType.setTextColor(getSecondaryTextColor()); + binding.key.setTextColor(getPrimaryTextColor()); + binding.keyType.setTextColor(getSecondaryTextColor()); if (status.isVerified()) { - verifiedFingerprintSymbol.setVisibility(View.VISIBLE); - verifiedFingerprintSymbol.setAlpha(1.0f); - trustToggle.setVisibility(View.GONE); - verifiedFingerprintSymbol.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - replaceToast(getString(R.string.this_device_has_been_verified), false); - } - }); + binding.verifiedFingerprint.setVisibility(View.VISIBLE); + binding.verifiedFingerprint.setAlpha(1.0f); + binding.tglTrust.setVisibility(View.GONE); + binding.verifiedFingerprint.setOnClickListener(v -> replaceToast(getString(R.string.this_device_has_been_verified), false)); toast = null; } else { - verifiedFingerprintSymbol.setVisibility(View.GONE); - trustToggle.setVisibility(View.VISIBLE); - trustToggle.setOnCheckedChangeListener(onCheckedChangeListener); + binding.verifiedFingerprint.setVisibility(View.GONE); + binding.tglTrust.setVisibility(View.VISIBLE); + binding.tglTrust.setOnCheckedChangeListener(onCheckedChangeListener); if (status.getTrust() == FingerprintStatus.Trust.UNDECIDED && undecidedNeedEnablement) { - trustToggle.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - account.getAxolotlService().setFingerprintTrust(fingerprint, FingerprintStatus.createActive(false)); - v.setEnabled(true); - v.setOnClickListener(null); - } + binding.buttonEnableDevice.setVisibility(View.VISIBLE); + binding.buttonEnableDevice.setOnClickListener(v -> { + account.getAxolotlService().setFingerprintTrust(fingerprint, FingerprintStatus.createActive(false)); + binding.buttonEnableDevice.setVisibility(View.GONE); + binding.tglTrust.setVisibility(View.VISIBLE); }); - trustToggle.setEnabled(false); + binding.tglTrust.setVisibility(View.GONE); } else { - trustToggle.setOnClickListener(null); - trustToggle.setEnabled(true); + binding.tglTrust.setOnClickListener(null); + binding.tglTrust.setEnabled(true); } - toast = new View.OnClickListener() { - @Override - public void onClick(View v) { - hideToast(); - } - }; + toast = v -> hideToast(); } } else { - key.setTextColor(getTertiaryTextColor()); - keyType.setTextColor(getTertiaryTextColor()); - toast = new View.OnClickListener() { - @Override - public void onClick(View v) { - replaceToast(getString(R.string.this_device_is_no_longer_in_use), false); - } - }; + binding.key.setTextColor(getTertiaryTextColor()); + binding.keyType.setTextColor(getTertiaryTextColor()); + toast = v -> replaceToast(getString(R.string.this_device_is_no_longer_in_use), false); if (status.isVerified()) { - trustToggle.setVisibility(View.GONE); - verifiedFingerprintSymbol.setVisibility(View.VISIBLE); - verifiedFingerprintSymbol.setAlpha(0.4368f); - verifiedFingerprintSymbol.setOnClickListener(toast); + binding.tglTrust.setVisibility(View.GONE); + binding.verifiedFingerprint.setVisibility(View.VISIBLE); + binding.verifiedFingerprint.setAlpha(0.4368f); + binding.verifiedFingerprint.setOnClickListener(toast); } else { - trustToggle.setVisibility(View.VISIBLE); - verifiedFingerprintSymbol.setVisibility(View.GONE); - trustToggle.setOnClickListener(null); - trustToggle.setEnabled(false); - trustToggle.setOnClickListener(toast); + binding.tglTrust.setVisibility(View.VISIBLE); + binding.verifiedFingerprint.setVisibility(View.GONE); + binding.tglTrust.setOnClickListener(null); + binding.tglTrust.setEnabled(false); + binding.tglTrust.setOnClickListener(toast); } } - view.setOnClickListener(toast); - key.setOnClickListener(toast); - keyType.setOnClickListener(toast); + binding.getRoot().setOnClickListener(toast); + binding.key.setOnClickListener(toast); + binding.keyType.setOnClickListener(toast); if (showTag) { - keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint)); + binding.keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint)); } else { - keyType.setVisibility(View.GONE); + binding.keyType.setVisibility(View.GONE); } if (highlight) { - keyType.setTextColor(ContextCompat.getColor(this, R.color.accent)); - keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509_selected_message : R.string.omemo_fingerprint_selected_message)); + binding.keyType.setTextColor(ContextCompat.getColor(this, R.color.accent)); + binding.keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509_selected_message : R.string.omemo_fingerprint_selected_message)); } else { - keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint)); + binding.keyType.setText(getString(x509 ? R.string.omemo_fingerprint_x509 : R.string.omemo_fingerprint)); } - key.setText(CryptoHelper.prettifyFingerprint(fingerprint.substring(2))); - - keys.addView(view); + binding.key.setText(CryptoHelper.prettifyFingerprint(fingerprint.substring(2))); } public void showPurgeKeyDialog(final Account account, final String fingerprint) { @@ -245,12 +210,9 @@ public abstract class OmemoActivity extends XmppActivity { builder.setMessage(R.string.distrust_omemo_key_text); builder.setNegativeButton(getString(R.string.cancel), null); builder.setPositiveButton(R.string.confirm, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - account.getAxolotlService().distrustFingerprint(fingerprint); - refreshUi(); - } + (dialog, which) -> { + account.getAxolotlService().distrustFingerprint(fingerprint); + refreshUi(); }); builder.create().show(); } diff --git a/src/main/java/de/pixart/messenger/ui/RecordingActivity.java b/src/main/java/de/pixart/messenger/ui/RecordingActivity.java index cfa743c69..21462ebfa 100644 --- a/src/main/java/de/pixart/messenger/ui/RecordingActivity.java +++ b/src/main/java/de/pixart/messenger/ui/RecordingActivity.java @@ -8,6 +8,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.SystemClock; +import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.view.WindowManager; @@ -24,7 +25,7 @@ import de.pixart.messenger.Config; import de.pixart.messenger.R; import de.pixart.messenger.persistance.FileBackend; -public class RecordingActivity extends Activity implements View.OnClickListener { +public class RecordingActivity extends AppCompatActivity implements View.OnClickListener { private TextView mTimerTextView; private Button mCancelButton; diff --git a/src/main/java/de/pixart/messenger/ui/SetPresenceActivity.java b/src/main/java/de/pixart/messenger/ui/SetPresenceActivity.java index 18d28914e..d4bddb534 100644 --- a/src/main/java/de/pixart/messenger/ui/SetPresenceActivity.java +++ b/src/main/java/de/pixart/messenger/ui/SetPresenceActivity.java @@ -1,6 +1,7 @@ package de.pixart.messenger.ui; import android.content.Intent; +import android.databinding.DataBindingUtil; import android.os.Bundle; import android.os.Handler; import android.util.Pair; @@ -9,18 +10,13 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.EditText; import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.ScrollView; -import android.widget.Spinner; import android.widget.TextView; import java.util.List; import de.pixart.messenger.R; +import de.pixart.messenger.databinding.ActivitySetPresenceBinding; import de.pixart.messenger.entities.Account; import de.pixart.messenger.entities.ListItem; import de.pixart.messenger.entities.Presence; @@ -33,12 +29,8 @@ public class SetPresenceActivity extends XmppActivity implements View.OnClickLis protected Account mAccount; private List<PresenceTemplate> mTemplates; - //UI Elements - protected ScrollView mScrollView; - protected EditText mStatusMessage; - protected Spinner mShowSpinner; - protected CheckBox mAllAccounts; - protected LinearLayout mTemplatesView; + private ActivitySetPresenceBinding binding; + private Pair<Integer, Intent> mPostponedActivityResult; private Runnable onPresenceChanged = new Runnable() { @@ -50,24 +42,13 @@ public class SetPresenceActivity extends XmppActivity implements View.OnClickLis protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_set_presence); - mScrollView = findViewById(R.id.scroll_view); - mShowSpinner = findViewById(R.id.presence_show); + this.binding = DataBindingUtil.setContentView(this, R.layout.activity_set_presence); ArrayAdapter adapter = ArrayAdapter.createFromResource(this, R.array.presence_show_options, R.layout.simple_list_item); - mShowSpinner.setAdapter(adapter); - mShowSpinner.setSelection(1); - mStatusMessage = findViewById(R.id.presence_status_message); - mAllAccounts = findViewById(R.id.all_accounts); - mTemplatesView = findViewById(R.id.templates); - final Button changePresence = findViewById(R.id.change_presence); - changePresence.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - executeChangePresence(); - } - }); + this.binding.presenceShow.setAdapter(adapter); + this.binding.presenceShow.setSelection(1); + this.binding.changePresence.setOnClickListener(v -> executeChangePresence()); } @Override @@ -105,8 +86,8 @@ public class SetPresenceActivity extends XmppActivity implements View.OnClickLis private void executeChangePresence() { Presence.Status status = getStatusFromSpinner(); - boolean allAccounts = mAllAccounts.isChecked(); - String statusMessage = mStatusMessage.getText().toString().trim(); + boolean allAccounts = this.binding.allAccounts.isChecked(); + String statusMessage = this.binding.presenceStatusMessage.getText().toString().trim(); if (allAccounts && noAccountUsesPgp()) { xmppConnectionService.changeStatus(status, statusMessage); finish(); @@ -122,7 +103,7 @@ public class SetPresenceActivity extends XmppActivity implements View.OnClickLis } private Presence.Status getStatusFromSpinner() { - switch (mShowSpinner.getSelectedItemPosition()) { + switch (this.binding.presenceShow.getSelectedItemPosition()) { case 0: return Presence.Status.CHAT; case 2: @@ -139,19 +120,19 @@ public class SetPresenceActivity extends XmppActivity implements View.OnClickLis private void setStatusInSpinner(Presence.Status status) { switch (status) { case AWAY: - mShowSpinner.setSelection(2); + this.binding.presenceShow.setSelection(2); break; case XA: - mShowSpinner.setSelection(3); + this.binding.presenceShow.setSelection(3); break; case CHAT: - mShowSpinner.setSelection(0); + this.binding.presenceShow.setSelection(0); break; case DND: - mShowSpinner.setSelection(4); + this.binding.presenceShow.setSelection(4); break; default: - mShowSpinner.setSelection(1); + this.binding.presenceShow.setSelection(1); break; } } @@ -167,29 +148,29 @@ public class SetPresenceActivity extends XmppActivity implements View.OnClickLis if (mAccount != null) { setStatusInSpinner(mAccount.getPresenceStatus()); String message = mAccount.getPresenceStatusMessage(); - if (mStatusMessage.getText().length() == 0 && message != null) { - mStatusMessage.append(message); + if (this.binding.presenceStatusMessage.getText().length() == 0 && message != null) { + this.binding.presenceStatusMessage.append(message); } mTemplates = xmppConnectionService.getPresenceTemplates(mAccount); if (this.mPostponedActivityResult != null) { this.onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second); } boolean e = noAccountUsesPgp(); - mAllAccounts.setEnabled(e); - mAllAccounts.setTextColor(e ? getPrimaryTextColor() : getSecondaryTextColor()); + this.binding.allAccounts.setEnabled(e); + this.binding.allAccounts.setTextColor(e ? getPrimaryTextColor() : getSecondaryTextColor()); } redrawTemplates(); } private void redrawTemplates() { if (mTemplates == null || mTemplates.size() == 0) { - mTemplatesView.setVisibility(View.GONE); + this.binding.templates.setVisibility(View.GONE); } else { - mTemplatesView.removeAllViews(); - mTemplatesView.setVisibility(View.VISIBLE); + this.binding.templates.removeAllViews(); + this.binding.templates.setVisibility(View.VISIBLE); LayoutInflater inflater = getLayoutInflater(); for (PresenceTemplate template : mTemplates) { - View templateLayout = inflater.inflate(R.layout.presence_template, mTemplatesView, false); + View templateLayout = inflater.inflate(R.layout.presence_template, this.binding.templates, false); templateLayout.setTag(template); setListItemBackgroundOnView(templateLayout); templateLayout.setOnClickListener(this); @@ -202,7 +183,7 @@ public class SetPresenceActivity extends XmppActivity implements View.OnClickLis status.setText(tag.getName()); status.setBackgroundColor(tag.getColor()); message.setText(template.getStatusMessage()); - mTemplatesView.addView(templateLayout); + this.binding.templates.addView(templateLayout); } } } @@ -215,14 +196,9 @@ public class SetPresenceActivity extends XmppActivity implements View.OnClickLis } if (v.getId() == R.id.presence_template) { setStatusInSpinner(template.getStatus()); - mStatusMessage.getEditableText().clear(); - mStatusMessage.getEditableText().append(template.getStatusMessage()); - new Handler().post(new Runnable() { - @Override - public void run() { - mScrollView.smoothScrollTo(0, 0); - } - }); + this.binding.presenceStatusMessage.getEditableText().clear(); + this.binding.presenceStatusMessage.getEditableText().append(template.getStatusMessage()); + new Handler().post(() -> this.binding.scrollView.smoothScrollTo(0,0)); } else if (v.getId() == R.id.delete_button) { xmppConnectionService.databaseBackend.deletePresenceTemplate(template); mTemplates.remove(template); diff --git a/src/main/java/de/pixart/messenger/ui/SettingsActivity.java b/src/main/java/de/pixart/messenger/ui/SettingsActivity.java index 988f4be1e..cf1ccd658 100644 --- a/src/main/java/de/pixart/messenger/ui/SettingsActivity.java +++ b/src/main/java/de/pixart/messenger/ui/SettingsActivity.java @@ -1,6 +1,5 @@ package de.pixart.messenger.ui; -import android.app.AlertDialog; import android.app.FragmentManager; import android.content.Context; import android.content.DialogInterface; @@ -8,6 +7,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.OnSharedPreferenceChangeListener; import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.preference.CheckBoxPreference; @@ -16,12 +16,14 @@ import android.preference.Preference; import android.preference.PreferenceCategory; import android.preference.PreferenceManager; import android.preference.PreferenceScreen; +import android.support.v7.app.AlertDialog; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.widget.EditText; import android.widget.Toast; +import java.io.File; import java.security.KeyStoreException; import java.util.ArrayList; import java.util.Arrays; @@ -29,11 +31,11 @@ import java.util.Collections; import java.util.List; import java.util.Locale; -import de.duenndns.ssl.MemorizingTrustManager; import de.pixart.messenger.Config; import de.pixart.messenger.R; import de.pixart.messenger.entities.Account; import de.pixart.messenger.services.ExportLogsService; +import de.pixart.messenger.services.MemorizingTrustManager; import de.pixart.messenger.xmpp.XmppConnection; import de.pixart.messenger.xmpp.jid.InvalidJidException; import de.pixart.messenger.xmpp.jid.Jid; @@ -70,6 +72,10 @@ public class SettingsActivity extends XmppActivity implements mSettingsFragment = new SettingsFragment(); fm.beginTransaction().replace(android.R.id.content, mSettingsFragment).commit(); } + mSettingsFragment.setActivityIntent(getIntent()); + + this.mTheme = findTheme(); + setTheme(this.mTheme); } @Override @@ -83,7 +89,9 @@ public class SettingsActivity extends XmppActivity implements PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this); multiAccountPreference = mSettingsFragment.findPreference("enable_multi_accounts"); - isMultiAccountChecked = ((CheckBoxPreference) multiAccountPreference).isChecked(); + if (multiAccountPreference != null) { + isMultiAccountChecked = ((CheckBoxPreference) multiAccountPreference).isChecked(); + } ListPreference resources = (ListPreference) mSettingsFragment.findPreference("resource"); if (resources != null) { @@ -121,9 +129,8 @@ public class SettingsActivity extends XmppActivity implements } final Preference removeCertsPreference = mSettingsFragment.findPreference("remove_trusted_certificates"); - removeCertsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { + if (removeCertsPreference != null) { + removeCertsPreference.setOnPreferenceClickListener(preference -> { final MemorizingTrustManager mtm = xmppConnectionService.getMemorizingTrustManager(); final ArrayList<String> aliases = Collections.list(mtm.getCertificates()); if (aliases.size() == 0) { @@ -134,44 +141,37 @@ public class SettingsActivity extends XmppActivity implements final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(SettingsActivity.this); dialogBuilder.setTitle(getResources().getString(R.string.dialog_manage_certs_title)); dialogBuilder.setMultiChoiceItems(aliases.toArray(new CharSequence[aliases.size()]), null, - new DialogInterface.OnMultiChoiceClickListener() { - @Override - public void onClick(DialogInterface dialog, int indexSelected, - boolean isChecked) { - if (isChecked) { - selectedItems.add(indexSelected); - } else if (selectedItems.contains(indexSelected)) { - selectedItems.remove(Integer.valueOf(indexSelected)); - } - if (selectedItems.size() > 0) - ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true); - else { - ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false); - } + (dialog, indexSelected, isChecked) -> { + if (isChecked) { + selectedItems.add(indexSelected); + } else if (selectedItems.contains(indexSelected)) { + selectedItems.remove(Integer.valueOf(indexSelected)); + } + if (selectedItems.size() > 0) + ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true); + else { + ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false); } }); dialogBuilder.setPositiveButton( - getResources().getString(R.string.dialog_manage_certs_positivebutton), new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - int count = selectedItems.size(); - if (count > 0) { - for (int i = 0; i < count; i++) { - try { - Integer item = Integer.valueOf(selectedItems.get(i).toString()); - String alias = aliases.get(item); - mtm.deleteCertificate(alias); - } catch (KeyStoreException e) { - e.printStackTrace(); - displayToast("Error: " + e.getLocalizedMessage()); - } - } - if (xmppConnectionServiceBound) { - reconnectAccounts(); + getResources().getString(R.string.dialog_manage_certs_positivebutton), (dialog, which) -> { + int count = selectedItems.size(); + if (count > 0) { + for (int i = 0; i < count; i++) { + try { + Integer item = Integer.valueOf(selectedItems.get(i).toString()); + String alias = aliases.get(item); + mtm.deleteCertificate(alias); + } catch (KeyStoreException e) { + e.printStackTrace(); + displayToast("Error: " + e.getLocalizedMessage()); } - displayToast(getResources().getQuantityString(R.plurals.toast_delete_certificates, count, count)); } + if (xmppConnectionServiceBound) { + reconnectAccounts(); + } + displayToast(getResources().getQuantityString(R.plurals.toast_delete_certificates, count, count)); } }); dialogBuilder.setNegativeButton(getResources().getString(R.string.dialog_manage_certs_negativebutton), null); @@ -179,33 +179,41 @@ public class SettingsActivity extends XmppActivity implements removeCertsDialog.show(); removeCertsDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); return true; - } - }); + }); + } final Preference exportLogsPreference = mSettingsFragment.findPreference("export_logs"); - exportLogsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { + if (exportLogsPreference != null) { + exportLogsPreference.setOnPreferenceClickListener(preference -> { if (hasStoragePermission(REQUEST_WRITE_LOGS)) { startExport(); } return true; + }); + } + + if (Config.ONLY_INTERNAL_STORAGE) { + final Preference cleanCachePreference = mSettingsFragment.findPreference("clean_cache"); + if (cleanCachePreference != null) { + cleanCachePreference.setOnPreferenceClickListener(preference -> cleanCache()); } - }); - final Preference deleteOmemoPreference = mSettingsFragment.findPreference("delete_omemo_identities"); - deleteOmemoPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - deleteOmemoIdentities(); - return true; + final Preference cleanPrivateStoragePreference = mSettingsFragment.findPreference("clean_private_storage"); + if (cleanPrivateStoragePreference != null) { + cleanPrivateStoragePreference.setOnPreferenceClickListener(preference -> cleanPrivateStorage()); } - }); + } + + final Preference deleteOmemoPreference = mSettingsFragment.findPreference("delete_omemo_identities"); + if (deleteOmemoPreference != null) { + deleteOmemoPreference.setOnPreferenceClickListener(preference -> deleteOmemoIdentities()); + } final Preference enableMultiAccountsPreference = mSettingsFragment.findPreference("enable_multi_accounts"); - Log.d(Config.LOGTAG, "Multi account checkbox checked: " + isMultiAccountChecked); - if (isMultiAccountChecked) { - enableMultiAccountsPreference.setEnabled(false); + if (enableMultiAccountsPreference != null) { + Log.d(Config.LOGTAG, "Multi account checkbox checked: " + isMultiAccountChecked); + if (isMultiAccountChecked) { + enableMultiAccountsPreference.setEnabled(false); /* if (xmppConnectionServiceBound) { // todo doesn't work --> it seems the service is never bound final List<Account> accounts = xmppConnectionService.getAccounts(); @@ -221,12 +229,13 @@ public class SettingsActivity extends XmppActivity implements enableMultiAccountsPreference.setEnabled(false); } */ - } else { - enableMultiAccountsPreference.setEnabled(true); - enableMultiAccountsPreference.setOnPreferenceClickListener(preference -> { - enableMultiAccounts(); - return true; - }); + } else { + enableMultiAccountsPreference.setEnabled(true); + enableMultiAccountsPreference.setOnPreferenceClickListener(preference -> { + enableMultiAccounts(); + return true; + }); + } } } @@ -234,7 +243,60 @@ public class SettingsActivity extends XmppActivity implements return i != null && getPackageManager().queryIntentActivities(i, PackageManager.MATCH_DEFAULT_ONLY).size() > 0; } - private void deleteOmemoIdentities() { + private boolean cleanCache() { + Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + intent.setData(Uri.parse("package:" + getPackageName())); + startActivity(intent); + return true; + } + + private boolean cleanPrivateStorage() { + cleanPrivatePictures(); + cleanPrivateFiles(); + return true; + } + + private void cleanPrivatePictures() { + try { + File dir = new File(getFilesDir().getAbsolutePath(), "/Pictures/"); + File[] array = dir.listFiles(); + if (array != null) { + for (int b = 0; b < array.length; b++) { + String name = array[b].getName().toLowerCase(); + if (name.equals(".nomedia")) { + continue; + } + if (array[b].isFile()) { + array[b].delete(); + } + } + } + } catch (Throwable e) { + Log.e("CleanCache", e.toString()); + } + } + + private void cleanPrivateFiles() { + try { + File dir = new File(getFilesDir().getAbsolutePath(), "/Files/"); + File[] array = dir.listFiles(); + if (array != null) { + for (int b = 0; b < array.length; b++) { + String name = array[b].getName().toLowerCase(); + if (name.equals(".nomedia")) { + continue; + } + if (array[b].isFile()) { + array[b].delete(); + } + } + } + } catch (Throwable e) { + Log.e("CleanCache", e.toString()); + } + } + + private boolean deleteOmemoIdentities() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.pref_delete_omemo_identities); final List<CharSequence> accounts = new ArrayList<>(); @@ -281,6 +343,7 @@ public class SettingsActivity extends XmppActivity implements AlertDialog dialog = builder.create(); dialog.show(); dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); + return true; } private void enableMultiAccounts() { diff --git a/src/main/java/de/pixart/messenger/ui/SettingsFragment.java b/src/main/java/de/pixart/messenger/ui/SettingsFragment.java index 8cea5eb61..939ef6145 100644 --- a/src/main/java/de/pixart/messenger/ui/SettingsFragment.java +++ b/src/main/java/de/pixart/messenger/ui/SettingsFragment.java @@ -1,64 +1,58 @@ package de.pixart.messenger.ui; -import android.app.Dialog; +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.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.widget.FrameLayout; -import android.widget.LinearLayout; +import android.text.TextUtils; +import de.pixart.messenger.Config; import de.pixart.messenger.R; public class SettingsFragment extends PreferenceFragment { - //http://stackoverflow.com/questions/16374820/action-bar-home-button-not-functional-with-nested-preferencescreen/16800527#16800527 - private void initializeActionBar(PreferenceScreen preferenceScreen) { - final Dialog dialog = preferenceScreen.getDialog(); + private String page = null; - if (dialog != null) { - View homeBtn = dialog.findViewById(android.R.id.home); + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.preferences); - if (homeBtn != null) { - View.OnClickListener dismissDialogClickListener = new View.OnClickListener() { - @Override - public void onClick(View v) { - dialog.dismiss(); - } - }; + // 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); + } + } - ViewParent homeBtnContainer = homeBtn.getParent(); + if (!TextUtils.isEmpty(page)) { + openPreferenceScreen(page); + } + } - if (homeBtnContainer instanceof FrameLayout) { - ViewGroup containerParent = (ViewGroup) homeBtnContainer.getParent(); - if (containerParent instanceof LinearLayout) { - containerParent.setOnClickListener(dismissDialogClickListener); - } else { - ((FrameLayout) homeBtnContainer).setOnClickListener(dismissDialogClickListener); - } - } else { - homeBtn.setOnClickListener(dismissDialogClickListener); + public void setActivityIntent(final Intent intent) { + if (intent != null) { + if (Intent.ACTION_VIEW.equals(intent.getAction())) { + if (intent.getExtras() != null) { + this.page = intent.getExtras().getString("page"); } } } } - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - // Load the preferences from an XML resource - addPreferencesFromResource(R.xml.preferences); - } - - @Override - public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) { - super.onPreferenceTreeClick(preferenceScreen, preference); - if (preference instanceof PreferenceScreen) { - initializeActionBar((PreferenceScreen) preference); + 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); } - return false; } } diff --git a/src/main/java/de/pixart/messenger/ui/ShareLocationActivity.java b/src/main/java/de/pixart/messenger/ui/ShareLocationActivity.java index 5a1122c7a..510135c5a 100644 --- a/src/main/java/de/pixart/messenger/ui/ShareLocationActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ShareLocationActivity.java @@ -41,9 +41,9 @@ public class ShareLocationActivity extends LocationActivity implements LocationL protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_share_locaction); - if (getActionBar() != null) { - getActionBar().setHomeButtonEnabled(true); - getActionBar().setDisplayHomeAsUpEnabled(true); + if (getSupportActionBar() != null) { + getSupportActionBar().setHomeButtonEnabled(true); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); } locationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE); diff --git a/src/main/java/de/pixart/messenger/ui/ShareViaAccountActivity.java b/src/main/java/de/pixart/messenger/ui/ShareViaAccountActivity.java index 41d22f2a5..e7ee427db 100644 --- a/src/main/java/de/pixart/messenger/ui/ShareViaAccountActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ShareViaAccountActivity.java @@ -1,7 +1,7 @@ package de.pixart.messenger.ui; -import android.app.ActionBar; import android.os.Bundle; +import android.support.v7.app.ActionBar; import android.view.View; import android.widget.AdapterView; import android.widget.AdapterView.OnItemClickListener; @@ -31,7 +31,7 @@ public class ShareViaAccountActivity extends XmppActivity { accountList.clear(); accountList.addAll(xmppConnectionService.getAccounts()); } - ActionBar actionBar = getActionBar(); + ActionBar actionBar = getSupportActionBar(); if (actionBar != null) { actionBar.setHomeButtonEnabled(this.accountList.size() > 0); actionBar.setDisplayHomeAsUpEnabled(this.accountList.size() > 0); diff --git a/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java b/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java index 15e7b7267..64671c1f2 100644 --- a/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ShareWithActivity.java @@ -170,9 +170,9 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer super.onCreate(savedInstanceState); boolean useBundledEmoji = getPreferences().getBoolean(USE_BUNDLED_EMOJIS, getResources().getBoolean(R.bool.use_bundled_emoji)); new EmojiService(this).init(useBundledEmoji); - if (getActionBar() != null) { - getActionBar().setDisplayHomeAsUpEnabled(false); - getActionBar().setHomeButtonEnabled(false); + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(false); + getSupportActionBar().setHomeButtonEnabled(false); } setContentView(R.layout.share_with); diff --git a/src/main/java/de/pixart/messenger/ui/ShowFullscreenMessageActivity.java b/src/main/java/de/pixart/messenger/ui/ShowFullscreenMessageActivity.java index 619ab2101..83928d0cd 100644 --- a/src/main/java/de/pixart/messenger/ui/ShowFullscreenMessageActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ShowFullscreenMessageActivity.java @@ -1,6 +1,5 @@ package de.pixart.messenger.ui; -import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Intent; import android.content.SharedPreferences; @@ -11,6 +10,7 @@ import android.media.MediaMetadataRetriever; import android.net.Uri; import android.os.Bundle; import android.preference.PreferenceManager; +import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.View; import android.view.WindowManager; @@ -39,7 +39,7 @@ import de.pixart.messenger.utils.ExifHelper; import static de.pixart.messenger.persistance.FileBackend.close; -public class ShowFullscreenMessageActivity extends Activity { +public class ShowFullscreenMessageActivity extends AppCompatActivity { Integer oldOrientation; PhotoView mImage; diff --git a/src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java b/src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java index 2f844e107..2661589d8 100644 --- a/src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java @@ -1,6 +1,5 @@ package de.pixart.messenger.ui; -import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; @@ -12,6 +11,7 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.preference.PreferenceManager; +import android.support.v7.app.AppCompatActivity; import android.text.TextUtils; import android.view.Menu; import android.view.MenuInflater; @@ -30,7 +30,7 @@ import de.pixart.messenger.services.EmojiService; import static de.pixart.messenger.ui.SettingsActivity.USE_BUNDLED_EMOJIS; -public class ShowLocationActivity extends Activity { +public class ShowLocationActivity extends AppCompatActivity { private Location location; private String mLocationName; @@ -39,9 +39,9 @@ public class ShowLocationActivity extends Activity { super.onCreate(savedInstanceState); boolean useBundledEmoji = getPreferences().getBoolean(USE_BUNDLED_EMOJIS, getResources().getBoolean(R.bool.use_bundled_emoji)); new EmojiService(this).init(useBundledEmoji); - if (getActionBar() != null) { - getActionBar().setHomeButtonEnabled(true); - getActionBar().setDisplayHomeAsUpEnabled(true); + if (getSupportActionBar() != null) { + getSupportActionBar().setHomeButtonEnabled(true); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); } setContentView(R.layout.activity_show_locaction); diff --git a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java index c1cd8a015..dacc35a8d 100644 --- a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java +++ b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java @@ -2,15 +2,7 @@ package de.pixart.messenger.ui; import android.Manifest; import android.annotation.SuppressLint; -import android.app.ActionBar; -import android.app.ActionBar.Tab; -import android.app.ActionBar.TabListener; -import android.app.AlertDialog; import android.app.Dialog; -import android.app.Fragment; -import android.app.FragmentManager; -import android.app.FragmentTransaction; -import android.app.ListFragment; import android.app.PendingIntent; import android.content.ActivityNotFoundException; import android.content.Context; @@ -18,11 +10,21 @@ import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.content.pm.PackageManager; +import android.databinding.DataBindingUtil; import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; +import android.support.v4.app.Fragment; +import android.support.v4.app.FragmentManager; +import android.support.v4.app.FragmentTransaction; +import android.support.v4.app.ListFragment; +import android.support.v4.view.MenuItemCompat; import android.support.v4.view.PagerAdapter; import android.support.v4.view.ViewPager; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AlertDialog; import android.text.Editable; import android.text.SpannableString; import android.text.Spanned; @@ -39,7 +41,6 @@ import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView; import android.widget.AdapterView.AdapterContextMenuInfo; -import android.widget.AdapterView.OnItemClickListener; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; import android.widget.CheckBox; @@ -58,6 +59,7 @@ import java.util.concurrent.atomic.AtomicBoolean; import de.pixart.messenger.Config; import de.pixart.messenger.R; +import de.pixart.messenger.databinding.ActivityStartConversationBinding; import de.pixart.messenger.entities.Account; import de.pixart.messenger.entities.Bookmark; import de.pixart.messenger.entities.Contact; @@ -78,13 +80,10 @@ import static de.pixart.messenger.ui.SettingsActivity.USE_BUNDLED_EMOJIS; public class StartConversationActivity extends XmppActivity implements OnRosterUpdate, OnUpdateBlocklist { - private final int REQUEST_SYNC_CONTACTS = 0x3b28cf; - private final int REQUEST_CREATE_CONFERENCE = 0x3b39da; + private final int REQUEST_SYNC_CONTACTS = 0x28cf; + private final int REQUEST_CREATE_CONFERENCE = 0x39da; public int conference_context_id; public int contact_context_id; - private Tab mContactsTab; - private Tab mConferencesTab; - private ViewPager mViewPager; private ListPagerAdapter mListPagerAdapter; private List<ListItem> contacts = new ArrayList<>(); private ListItemAdapter mContactsAdapter; @@ -119,29 +118,30 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU return true; } }; - private TabListener mTabListener = new TabListener() { + private ActionBar.TabListener mTabListener = new ActionBar.TabListener() { @Override - public void onTabUnselected(Tab tab, FragmentTransaction ft) { + public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) { return; } @Override - public void onTabSelected(Tab tab, FragmentTransaction ft) { - mViewPager.setCurrentItem(tab.getPosition()); + public void onTabSelected(ActionBar.Tab tab, FragmentTransaction ft) { + binding.startConversationViewPager.setCurrentItem(tab.getPosition()); onTabChanged(); } @Override - public void onTabReselected(Tab tab, FragmentTransaction ft) { + public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) { return; } }; private ViewPager.SimpleOnPageChangeListener mOnPageChangeListener = new ViewPager.SimpleOnPageChangeListener() { @Override public void onPageSelected(int position) { - if (getActionBar() != null) { - getActionBar().setSelectedNavigationItem(position); + ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setSelectedNavigationItem(position); } onTabChanged(); } @@ -166,7 +166,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU private TextView.OnEditorActionListener mSearchDone = new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { - int pos = getActionBar().getSelectedNavigationIndex(); + int pos = getSupportActionBar().getSelectedNavigationIndex(); if (pos == 0) { if (contacts.size() == 1) { openConversationForContact((Contact) contacts.get(0)); @@ -198,6 +198,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU private String mInitialJid; private Pair<Integer, Intent> mPostponedActivityResult; private Toast mToast; + private ActivityStartConversationBinding binding; private UiCallback<Conversation> mAdhocConferenceCallback = new UiCallback<Conversation>() { @Override public void success(final Conversation conversation) { @@ -255,21 +256,25 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); new EmojiService(this).init(useBundledEmoji()); - setContentView(R.layout.activity_start_conversation); - mViewPager = findViewById(R.id.start_conversation_view_pager); - ActionBar actionBar = getActionBar(); + this.binding = DataBindingUtil.setContentView(this, R.layout.activity_start_conversation); + this.binding.fab.setOnClickListener((v) -> { + if (getSupportActionBar().getSelectedNavigationIndex() == 0) { + showCreateContactDialog(null, null); + } else { + showCreateConferenceDialog(); + } + }); + ActionBar actionBar = getSupportActionBar(); actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); - mContactsTab = actionBar.newTab().setText(R.string.contacts) - .setTabListener(mTabListener); - mConferencesTab = actionBar.newTab().setText(R.string.conferences) - .setTabListener(mTabListener); + ActionBar.Tab mContactsTab = actionBar.newTab().setText(R.string.contacts).setTabListener(mTabListener); + ActionBar.Tab mConferencesTab = actionBar.newTab().setText(R.string.conferences).setTabListener(mTabListener); actionBar.addTab(mContactsTab); actionBar.addTab(mConferencesTab); - mViewPager.setOnPageChangeListener(mOnPageChangeListener); - mListPagerAdapter = new ListPagerAdapter(getFragmentManager()); - mViewPager.setAdapter(mListPagerAdapter); + binding.startConversationViewPager.setOnPageChangeListener(mOnPageChangeListener); + mListPagerAdapter = new ListPagerAdapter(getSupportFragmentManager()); + binding.startConversationViewPager.setAdapter(mListPagerAdapter); mConferenceAdapter = new ListItemAdapter(this, conferences); mContactsAdapter = new ListItemAdapter(this, contacts); @@ -595,8 +600,6 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.start_conversation, menu); - MenuItem menuCreateContact = menu.findItem(R.id.action_create_contact); - MenuItem menuCreateConference = menu.findItem(R.id.action_conference); MenuItem menuHideOffline = menu.findItem(R.id.action_hide_offline); final MenuItem menuActionAccounts = menu.findItem(R.id.action_accounts); if (xmppConnectionService.getAccounts().size() == 1 && !xmppConnectionService.multipleAccounts()) { @@ -608,17 +611,11 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU mMenuSearchView = menu.findItem(R.id.action_search); mMenuSearchView.setOnActionExpandListener(mOnActionExpandListener); View mSearchView = mMenuSearchView.getActionView(); - mSearchEditText = mSearchView - .findViewById(R.id.search_field); + mSearchEditText = mSearchView.findViewById(R.id.search_field); mSearchEditText.addTextChangedListener(mSearchTextWatcher); mSearchEditText.setOnEditorActionListener(mSearchDone); - if (getActionBar().getSelectedNavigationIndex() == 0) { - menuCreateConference.setVisible(false); - } else { - menuCreateContact.setVisible(false); - } if (mInitialJid != null) { - mMenuSearchView.expandActionView(); + MenuItemCompat.expandActionView(mMenuSearchView); mSearchEditText.append(mInitialJid); filter(mInitialJid); } @@ -628,15 +625,9 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { - case R.id.action_create_contact: - showCreateContactDialog(null, null); - return true; case R.id.action_join_conference: showJoinConferenceDialog(null); return true; - case R.id.action_create_conference: - showCreateConferenceDialog(); - return true; case R.id.action_scan_qr_code: Intent intent = new Intent(this, UriHandlerActivity.class); intent.setAction(UriHandlerActivity.ACTION_SCAN_QR_CODE); @@ -752,7 +743,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU } @Override - public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) { + public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { if (grantResults.length > 0) if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (requestCode == REQUEST_SYNC_CONTACTS && xmppConnectionServiceBound) { @@ -778,7 +769,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU } } final Intent intent = getIntent(); - final ActionBar ab = getActionBar(); + final ActionBar ab = getSupportActionBar(); boolean init = intent != null && intent.getBooleanExtra("init", false); boolean noConversations = xmppConnectionService.getConversations().size() == 0; if ((init || noConversations) && ab != null) { @@ -945,6 +936,13 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU } private void onTabChanged() { + @DrawableRes final int fabDrawable; + if (getSupportActionBar().getSelectedNavigationIndex() == 0) { + fabDrawable = R.drawable.ic_person_add_white_24dp; + } else { + fabDrawable = R.drawable.ic_group_add_white_24dp; + } + binding.fab.setImageResource(fabDrawable); invalidateOptionsMenu(); } @@ -980,10 +978,12 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU } @Override - public void onViewCreated(final View view, final Bundle savedInstanceState) { + public void onViewCreated(@NonNull final View view, final Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); registerForContextMenu(getListView()); getListView().setFastScrollEnabled(true); + getListView().setDivider(null); + getListView().setDividerHeight(0); } @Override @@ -991,6 +991,9 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU final ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo); final StartConversationActivity activity = (StartConversationActivity) getActivity(); + if (activity == null) { + return; + } activity.getMenuInflater().inflate(mResContextMenu, menu); final AdapterView.AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo; if (mResContextMenu == R.menu.conference_context) { @@ -1019,6 +1022,9 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU @Override public boolean onContextItemSelected(final MenuItem item) { StartConversationActivity activity = (StartConversationActivity) getActivity(); + if (activity == null) { + return true; + } switch (item.getItemId()) { case R.id.context_start_conversation: activity.openConversationForContact(); @@ -1061,7 +1067,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU } @Override - public void destroyItem(ViewGroup container, int position, Object object) { + public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { assert (0 <= position && position < fragments.length); FragmentTransaction trans = fragmentManager.beginTransaction(); trans.remove(fragments[position]); @@ -1070,7 +1076,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU } @Override - public Fragment instantiateItem(ViewGroup container, int position) { + public Fragment instantiateItem(@NonNull ViewGroup container, int position) { Fragment fragment = getItem(position); FragmentTransaction trans = fragmentManager.beginTransaction(); trans.add(container.getId(), fragment, "fragment:" + position); @@ -1084,7 +1090,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU } @Override - public boolean isViewFromObject(View view, Object fragment) { + public boolean isViewFromObject(@NonNull View view, @NonNull Object fragment) { return ((Fragment) fragment).getView() == view; } @@ -1095,26 +1101,12 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU if (position == 1) { listFragment.setListAdapter(mConferenceAdapter); listFragment.setContextMenu(R.menu.conference_context); - listFragment.setOnListItemClickListener(new OnItemClickListener() { - - @Override - public void onItemClick(AdapterView<?> arg0, View arg1, - int position, long arg3) { - openConversationForBookmark(position); - } - }); + listFragment.setOnListItemClickListener((arg0, arg1, p, arg3) -> openConversationForBookmark(p)); } else { listFragment.setListAdapter(mContactsAdapter); listFragment.setContextMenu(R.menu.contact_context); - listFragment.setOnListItemClickListener(new OnItemClickListener() { - - @Override - public void onItemClick(AdapterView<?> arg0, View arg1, - int position, long arg3) { - openConversationForContact(position); - } - }); + listFragment.setOnListItemClickListener((arg0, arg1, p, arg3) -> openConversationForContact(p)); } fragments[position] = listFragment; } diff --git a/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java b/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java index 853b1b2a4..c36f0fcbc 100644 --- a/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java +++ b/src/main/java/de/pixart/messenger/ui/TrustKeysActivity.java @@ -1,18 +1,16 @@ package de.pixart.messenger.ui; -import android.app.ActionBar; import android.content.Intent; +import android.databinding.DataBindingUtil; import android.os.Bundle; +import android.support.v7.app.ActionBar; import android.util.Log; import android.view.Gravity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; -import android.widget.Button; import android.widget.CompoundButton; -import android.widget.LinearLayout; -import android.widget.TextView; import android.widget.Toast; import org.whispersystems.libsignal.IdentityKey; @@ -29,6 +27,8 @@ import de.pixart.messenger.Config; import de.pixart.messenger.R; import de.pixart.messenger.crypto.axolotl.AxolotlService; import de.pixart.messenger.crypto.axolotl.FingerprintStatus; +import de.pixart.messenger.databinding.ActivityTrustKeysBinding; +import de.pixart.messenger.databinding.KeysCardBinding; import de.pixart.messenger.entities.Account; import de.pixart.messenger.entities.Conversation; import de.pixart.messenger.utils.CryptoHelper; @@ -43,21 +43,13 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat private Account mAccount; private Conversation mConversation; - private TextView keyErrorMessage; - private LinearLayout keyErrorMessageCard; - private TextView ownKeysTitle; - private LinearLayout ownKeys; - private LinearLayout ownKeysCard; - private LinearLayout foreignKeys; - private Button mSaveButton; - private Button mCancelButton; private AtomicBoolean mUseCameraHintShown = new AtomicBoolean(false); private AxolotlService.FetchStatus lastFetchReport = AxolotlService.FetchStatus.SUCCESS; private final Map<String, Boolean> ownKeysToTrust = new HashMap<>(); - private final Map<Jid, Map<String, Boolean>> foreignKeysToTrust = new HashMap<>(); + private final Map<Jid,Map<String, Boolean>> foreignKeysToTrust = new HashMap<>(); private final OnClickListener mSaveButtonListener = new OnClickListener() { @Override @@ -74,9 +66,8 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat finish(); } }; - - private XmppUri mPendingFingerprintVerificationUri = null; private Toast mUseCameraHintToast = null; + private ActivityTrustKeysBinding binding; @Override protected void refreshUiReal() { @@ -87,9 +78,9 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_trust_keys); + this.binding = DataBindingUtil.setContentView(this,R.layout.activity_trust_keys); this.contactJids = new ArrayList<>(); - for (String jid : getIntent().getStringArrayExtra("contacts")) { + for(String jid : getIntent().getStringArrayExtra("contacts")) { try { this.contactJids.add(Jid.fromString(jid)); } catch (InvalidJidException e) { @@ -97,25 +88,17 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat } } - keyErrorMessageCard = findViewById(R.id.key_error_message_card); - keyErrorMessage = findViewById(R.id.key_error_message); - ownKeysTitle = findViewById(R.id.own_keys_title); - ownKeys = findViewById(R.id.own_keys_details); - ownKeysCard = findViewById(R.id.own_keys_card); - foreignKeys = findViewById(R.id.foreign_keys); - mCancelButton = findViewById(R.id.cancel_button); - mCancelButton.setOnClickListener(mCancelButtonListener); - mSaveButton = findViewById(R.id.save_button); - mSaveButton.setOnClickListener(mSaveButtonListener); - - - if (getActionBar() != null) { - getActionBar().setHomeButtonEnabled(true); - getActionBar().setDisplayHomeAsUpEnabled(true); + binding.cancelButton.setOnClickListener(mCancelButtonListener); + binding.saveButton.setOnClickListener(mSaveButtonListener); + + + if (getSupportActionBar() != null) { + getSupportActionBar().setHomeButtonEnabled(true); + getSupportActionBar().setDisplayHomeAsUpEnabled(true); } if (savedInstanceState != null) { - mUseCameraHintShown.set(savedInstanceState.getBoolean("camera_hint_shown", false)); + mUseCameraHintShown.set(savedInstanceState.getBoolean("camera_hint_shown",false)); } } @@ -135,7 +118,7 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat private void showCameraToast() { mUseCameraHintToast = Toast.makeText(this,R.string.use_camera_icon_to_scan_barcode,Toast.LENGTH_LONG); - ActionBar actionBar = getActionBar(); + ActionBar actionBar = getSupportActionBar(); mUseCameraHintToast.setGravity(Gravity.TOP | Gravity.END, 0 ,actionBar == null ? 0 : actionBar.getHeight()); mUseCameraHintToast.show(); } @@ -187,13 +170,13 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat private void populateView() { setTitle(getString(R.string.trust_omemo_fingerprints)); - ownKeys.removeAllViews(); - foreignKeys.removeAllViews(); + binding.ownKeysDetails.removeAllViews(); + binding.foreignKeys.removeAllViews(); boolean hasOwnKeys = false; boolean hasForeignKeys = false; - for (final String fingerprint : ownKeysToTrust.keySet()) { + for(final String fingerprint : ownKeysToTrust.keySet()) { hasOwnKeys = true; - addFingerprintRowWithListeners(ownKeys, mAccount, fingerprint, false, + addFingerprintRowWithListeners(binding.ownKeysDetails, mAccount, fingerprint, false, FingerprintStatus.createActive(ownKeysToTrust.get(fingerprint)), false, false, new CompoundButton.OnCheckedChangeListener() { @Override @@ -208,84 +191,74 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat synchronized (this.foreignKeysToTrust) { for (Map.Entry<Jid, Map<String, Boolean>> entry : foreignKeysToTrust.entrySet()) { hasForeignKeys = true; - final LinearLayout layout = (LinearLayout) getLayoutInflater().inflate(R.layout.keys_card, foreignKeys, false); + KeysCardBinding keysCardBinding = DataBindingUtil.inflate(getLayoutInflater(),R.layout.keys_card, binding.foreignKeys,false); + //final LinearLayout layout = (LinearLayout) getLayoutInflater().inflate(R.layout.keys_card, foreignKeys, false); final Jid jid = entry.getKey(); - final TextView header = layout.findViewById(R.id.foreign_keys_title); - final LinearLayout keysContainer = layout.findViewById(R.id.foreign_keys_details); - final TextView informNoKeys = layout.findViewById(R.id.no_keys_to_accept); - header.setText(jid.toString()); - header.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - switchToContactDetails(mAccount.getRoster().getContact(jid)); - } - }); + keysCardBinding.foreignKeysTitle.setText(jid.toString()); + keysCardBinding.foreignKeysTitle.setOnClickListener(v -> switchToContactDetails(mAccount.getRoster().getContact(jid))); final Map<String, Boolean> fingerprints = entry.getValue(); for (final String fingerprint : fingerprints.keySet()) { - addFingerprintRowWithListeners(keysContainer, mAccount, fingerprint, false, + addFingerprintRowWithListeners(keysCardBinding.foreignKeysDetails, mAccount, fingerprint, false, FingerprintStatus.createActive(fingerprints.get(fingerprint)), false, false, - new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - fingerprints.put(fingerprint, isChecked); - lockOrUnlockAsNeeded(); - } + (buttonView, isChecked) -> { + fingerprints.put(fingerprint, isChecked); + lockOrUnlockAsNeeded(); } ); } if (fingerprints.size() == 0) { - informNoKeys.setVisibility(View.VISIBLE); + keysCardBinding.noKeysToAccept.setVisibility(View.VISIBLE); if (hasNoOtherTrustedKeys(jid)) { if (!mAccount.getRoster().getContact(jid).mutualPresenceSubscription()) { - informNoKeys.setText(R.string.error_no_keys_to_trust_presence); + keysCardBinding.noKeysToAccept.setText(R.string.error_no_keys_to_trust_presence); } else { - informNoKeys.setText(R.string.error_no_keys_to_trust_server_error); + keysCardBinding.noKeysToAccept.setText(R.string.error_no_keys_to_trust_server_error); } } else { - informNoKeys.setText(getString(R.string.no_keys_just_confirm, mAccount.getRoster().getContact(jid).getDisplayName())); + keysCardBinding.noKeysToAccept.setText(getString(R.string.no_keys_just_confirm, mAccount.getRoster().getContact(jid).getDisplayName())); } } else { - informNoKeys.setVisibility(View.GONE); + keysCardBinding.noKeysToAccept.setVisibility(View.GONE); } - foreignKeys.addView(layout); + binding.foreignKeys.addView(keysCardBinding.foreignKeysCard); } } - if ((hasOwnKeys || foreignActuallyHasKeys()) && mUseCameraHintShown.compareAndSet(false, true)) { + if ((hasOwnKeys || foreignActuallyHasKeys()) && mUseCameraHintShown.compareAndSet(false,true)) { showCameraToast(); } - ownKeysTitle.setText(mAccount.getJid().toBareJid().toString()); - ownKeysCard.setVisibility(hasOwnKeys ? View.VISIBLE : View.GONE); - foreignKeys.setVisibility(hasForeignKeys ? View.VISIBLE : View.GONE); - if (hasPendingKeyFetches()) { + binding.ownKeysTitle.setText(mAccount.getJid().toBareJid().toString()); + binding.ownKeysCard.setVisibility(hasOwnKeys ? View.VISIBLE : View.GONE); + binding.foreignKeys.setVisibility(hasForeignKeys ? View.VISIBLE : View.GONE); + if(hasPendingKeyFetches()) { setFetching(); lock(); } else { if (!hasForeignKeys && hasNoOtherTrustedKeys()) { - keyErrorMessageCard.setVisibility(View.VISIBLE); + binding.keyErrorMessageCard.setVisibility(View.VISIBLE); if (lastFetchReport == AxolotlService.FetchStatus.ERROR || mAccount.getAxolotlService().fetchMapHasErrors(contactJids)) { if (anyWithoutMutualPresenceSubscription(contactJids)) { - keyErrorMessage.setText(R.string.error_no_keys_to_trust_presence); + binding.keyErrorMessage.setText(R.string.error_no_keys_to_trust_presence); } else { - keyErrorMessage.setText(R.string.error_no_keys_to_trust_server_error); + binding.keyErrorMessage.setText(R.string.error_no_keys_to_trust_server_error); } } else { - keyErrorMessage.setText(R.string.error_no_keys_to_trust); + binding.keyErrorMessage.setText(R.string.error_no_keys_to_trust); } - ownKeys.removeAllViews(); - ownKeysCard.setVisibility(View.GONE); - foreignKeys.removeAllViews(); - foreignKeys.setVisibility(View.GONE); + binding.ownKeysDetails.removeAllViews(); + binding.ownKeysCard.setVisibility(View.GONE); + binding.foreignKeys.removeAllViews(); + binding.foreignKeys.setVisibility(View.GONE); } lockOrUnlockAsNeeded(); setDone(); } } - private boolean anyWithoutMutualPresenceSubscription(List<Jid> contactJids) { - for (Jid jid : contactJids) { + private boolean anyWithoutMutualPresenceSubscription(List<Jid> contactJids){ + for(Jid jid : contactJids) { if (!mAccount.getRoster().getContact(jid).mutualPresenceSubscription()) { return true; } @@ -309,9 +282,9 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat ownKeysToTrust.clear(); AxolotlService service = this.mAccount.getAxolotlService(); Set<IdentityKey> ownKeysSet = service.getKeysWithTrust(FingerprintStatus.createActiveUndecided()); - for (final IdentityKey identityKey : ownKeysSet) { + for(final IdentityKey identityKey : ownKeysSet) { final String fingerprint = CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize()); - if (!ownKeysToTrust.containsKey(fingerprint)) { + if(!ownKeysToTrust.containsKey(fingerprint)) { ownKeysToTrust.put(fingerprint, false); } } @@ -380,7 +353,7 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat } switch (report) { case ERROR: - Toast.makeText(TrustKeysActivity.this, R.string.error_fetching_omemo_key, Toast.LENGTH_SHORT).show(); + Toast.makeText(TrustKeysActivity.this,R.string.error_fetching_omemo_key,Toast.LENGTH_SHORT).show(); break; case SUCCESS_TRUSTED: Toast.makeText(TrustKeysActivity.this,R.string.blindly_trusted_omemo_keys,Toast.LENGTH_LONG).show(); @@ -416,7 +389,7 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat } private void commitTrusts() { - for (final String fingerprint : ownKeysToTrust.keySet()) { + for(final String fingerprint :ownKeysToTrust.keySet()) { mAccount.getAxolotlService().setFingerprintTrust( fingerprint, FingerprintStatus.createActive(ownKeysToTrust.get(fingerprint))); @@ -443,13 +416,13 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat } private void unlock() { - mSaveButton.setEnabled(true); - mSaveButton.setTextColor(getPrimaryTextColor()); + binding.saveButton.setEnabled(true); + binding.saveButton.setTextColor(getPrimaryTextColor()); } private void lock() { - mSaveButton.setEnabled(false); - mSaveButton.setTextColor(getSecondaryTextColor()); + binding.saveButton.setEnabled(false); + binding.saveButton.setTextColor(getSecondaryTextColor()); } private void lockOrUnlockAsNeeded() { @@ -467,10 +440,10 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat } private void setDone() { - mSaveButton.setText(getString(R.string.done)); + binding.saveButton.setText(getString(R.string.done)); } private void setFetching() { - mSaveButton.setText(getString(R.string.fetching_keys)); + binding.saveButton.setText(getString(R.string.fetching_keys)); } -} +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/UpdaterActivity.java b/src/main/java/de/pixart/messenger/ui/UpdaterActivity.java index 8a23741ef..f62454510 100644 --- a/src/main/java/de/pixart/messenger/ui/UpdaterActivity.java +++ b/src/main/java/de/pixart/messenger/ui/UpdaterActivity.java @@ -1,7 +1,7 @@ package de.pixart.messenger.ui; import android.Manifest; -import android.app.AlertDialog; +import android.support.v7.app.AlertDialog; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; diff --git a/src/main/java/de/pixart/messenger/ui/UriHandlerActivity.java b/src/main/java/de/pixart/messenger/ui/UriHandlerActivity.java index 262be5bb0..09d4d26ac 100644 --- a/src/main/java/de/pixart/messenger/ui/UriHandlerActivity.java +++ b/src/main/java/de/pixart/messenger/ui/UriHandlerActivity.java @@ -1,8 +1,8 @@ package de.pixart.messenger.ui; -import android.app.Activity; import android.content.Intent; import android.net.Uri; +import android.support.v7.app.AppCompatActivity; import java.util.Arrays; import java.util.List; @@ -13,7 +13,7 @@ import de.pixart.messenger.utils.zxing.IntentIntegrator; import de.pixart.messenger.utils.zxing.IntentResult; import de.pixart.messenger.xmpp.jid.Jid; -public class UriHandlerActivity extends Activity { +public class UriHandlerActivity extends AppCompatActivity { public static final String ACTION_SCAN_QR_CODE = "scan_qr_code"; @Override diff --git a/src/main/java/de/pixart/messenger/ui/VerifyOTRActivity.java b/src/main/java/de/pixart/messenger/ui/VerifyOTRActivity.java index a64f39a03..ea9bcc5a8 100644 --- a/src/main/java/de/pixart/messenger/ui/VerifyOTRActivity.java +++ b/src/main/java/de/pixart/messenger/ui/VerifyOTRActivity.java @@ -1,10 +1,10 @@ package de.pixart.messenger.ui; -import android.app.ActionBar; -import android.app.AlertDialog; +import android.support.v7.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; +import android.support.v7.app.ActionBar; import android.view.Menu; import android.view.View; import android.widget.Button; @@ -251,7 +251,7 @@ public class VerifyOTRActivity extends XmppActivity implements XmppConnectionSer protected void updateView() { if (this.mConversation != null && this.mConversation.hasValidOtrSession()) { - final ActionBar actionBar = getActionBar(); + final ActionBar actionBar = getSupportActionBar(); this.mVerificationExplain.setText(R.string.no_otr_session_found); invalidateOptionsMenu(); switch (this.mode) { diff --git a/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java b/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java index 0e84d1d64..0c02183c6 100644 --- a/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java +++ b/src/main/java/de/pixart/messenger/ui/WelcomeActivity.java @@ -1,9 +1,7 @@ package de.pixart.messenger.ui; import android.Manifest; -import android.app.ActionBar; -import android.app.Activity; -import android.app.AlertDialog; +import android.support.v7.app.AlertDialog; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; @@ -13,6 +11,8 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteException; import android.os.Build; import android.os.Bundle; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; import android.util.Log; import android.view.LayoutInflater; import android.view.View; @@ -79,7 +79,7 @@ public class WelcomeActivity extends XmppActivity { } super.onCreate(savedInstanceState); setContentView(R.layout.welcome); - final ActionBar ab = getActionBar(); + final ActionBar ab = getSupportActionBar(); if (ab != null) { ab.setDisplayShowHomeEnabled(false); ab.setDisplayHomeAsUpEnabled(false); @@ -336,7 +336,7 @@ public class WelcomeActivity extends XmppActivity { } } - public static void launch(Activity activity) { + public static void launch(AppCompatActivity activity) { Intent intent = new Intent(activity, WelcomeActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION); activity.startActivity(intent); diff --git a/src/main/java/de/pixart/messenger/ui/XmppActivity.java b/src/main/java/de/pixart/messenger/ui/XmppActivity.java index 79f528f93..7e0df64ca 100644 --- a/src/main/java/de/pixart/messenger/ui/XmppActivity.java +++ b/src/main/java/de/pixart/messenger/ui/XmppActivity.java @@ -3,9 +3,7 @@ package de.pixart.messenger.ui; import android.Manifest; import android.annotation.SuppressLint; import android.annotation.TargetApi; -import android.app.ActionBar; -import android.app.Activity; -import android.app.AlertDialog; +import android.support.v7.app.AlertDialog; import android.app.AlertDialog.Builder; import android.app.PendingIntent; import android.content.ActivityNotFoundException; @@ -39,6 +37,8 @@ import android.os.SystemClock; import android.preference.PreferenceManager; import android.provider.Settings; import android.support.v4.content.ContextCompat; +import android.support.v7.app.ActionBar; +import android.support.v7.app.AppCompatActivity; import android.text.InputType; import android.util.DisplayMetrics; import android.util.Log; @@ -83,7 +83,7 @@ import de.pixart.messenger.xmpp.OnUpdateBlocklist; import de.pixart.messenger.xmpp.jid.InvalidJidException; import de.pixart.messenger.xmpp.jid.Jid; -public abstract class XmppActivity extends Activity { +public abstract class XmppActivity extends AppCompatActivity { protected static final int REQUEST_ANNOUNCE_PGP = 0x0101; protected static final int REQUEST_INVITE_TO_CONVERSATION = 0x0102; @@ -276,7 +276,7 @@ public abstract class XmppActivity extends Activity { } public void showInstallPgpDialog() { - Builder builder = new AlertDialog.Builder(this); + AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(getString(R.string.openkeychain_required)); builder.setIconAttribute(android.R.attr.alertDialogIcon); builder.setMessage(getText(R.string.openkeychain_required_long)); @@ -437,7 +437,7 @@ public abstract class XmppActivity extends Activity { setTheme(this.mTheme); this.mUsingEnterKey = usingEnterKey(); mUseSubject = getPreferences().getBoolean("use_subject", getResources().getBoolean(R.bool.use_subject)); - final ActionBar ab = getActionBar(); + final ActionBar ab = getSupportActionBar(); if (ab != null) { ab.setDisplayHomeAsUpEnabled(true); } @@ -565,8 +565,7 @@ public abstract class XmppActivity extends Activity { } protected void inviteToConversation(Conversation conversation) { - Intent intent = new Intent(getApplicationContext(), - ChooseContactActivity.class); + Intent intent = new Intent(this, ChooseContactActivity.class); List<String> contacts = new ArrayList<>(); if (conversation.getMode() == Conversation.MODE_MULTI) { for (MucOptions.User user : conversation.getMucOptions().getUsers(false)) { diff --git a/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java index 451e326e6..792d365ef 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/AccountAdapter.java @@ -134,7 +134,8 @@ public class AccountAdapter extends ArrayAdapter<Account> { @Override protected Bitmap doInBackground(Account... params) { - return activity.avatarService().get(params[0], activity.getPixel(56), isCancelled()); + this.account = params[0]; + return activity.avatarService().get(this.account, activity.getPixel(56), isCancelled()); } @Override diff --git a/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java index 162455732..3c2de008e 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java @@ -357,7 +357,8 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { @Override protected Bitmap doInBackground(Conversation... params) { - return activity.avatarService().get(params[0], activity.getPixel(56)); + this.conversation = params[0]; + return activity.avatarService().get(this.conversation, activity.getPixel(56), isCancelled()); } @Override diff --git a/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java index 8b4af00d8..2df1e5b0f 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java @@ -1,8 +1,8 @@ package de.pixart.messenger.ui.adapter; -import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; +import android.databinding.DataBindingUtil; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; @@ -23,6 +23,7 @@ import java.util.List; import java.util.concurrent.RejectedExecutionException; import de.pixart.messenger.R; +import de.pixart.messenger.databinding.ContactBinding; import de.pixart.messenger.entities.ListItem; import de.pixart.messenger.ui.SettingsActivity; import de.pixart.messenger.ui.XmppActivity; @@ -60,15 +61,17 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> { @Override public View getView(int position, View view, ViewGroup parent) { - LayoutInflater inflater = (LayoutInflater) getContext() - .getSystemService(Context.LAYOUT_INFLATER_SERVICE); + LayoutInflater inflater = activity.getLayoutInflater(); ListItem item = getItem(position); + ViewHolder viewHolder; if (view == null) { - view = inflater.inflate(R.layout.contact, parent, false); + ContactBinding binding = DataBindingUtil.inflate(inflater, R.layout.contact, parent, false); + viewHolder = ViewHolder.get(binding); + view = binding.getRoot(); + } else { + viewHolder = (ViewHolder) view.getTag(); } - ViewHolder viewHolder = ViewHolder.get(view); - List<ListItem.Tag> tags = item.getTags(activity); if (tags.size() == 0 || !this.showDynamicTags) { @@ -137,7 +140,8 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> { @Override protected Bitmap doInBackground(ListItem... params) { - return activity.avatarService().get(params[0], activity.getPixel(48), isCancelled()); + this.item = params[0]; + return activity.avatarService().get(this.item, activity.getPixel(56), isCancelled()); } @Override @@ -221,17 +225,13 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> { private ViewHolder() { } - public static ViewHolder get(View layout) { - ViewHolder viewHolder = (ViewHolder) layout.getTag(); - if (viewHolder == null) { - viewHolder = new ViewHolder(); - - viewHolder.name = layout.findViewById(R.id.contact_display_name); - viewHolder.jid = layout.findViewById(R.id.contact_jid); - viewHolder.avatar = layout.findViewById(R.id.contact_photo); - viewHolder.tags = layout.findViewById(R.id.tags); - layout.setTag(viewHolder); - } + public static ViewHolder get(ContactBinding binding) { + ViewHolder viewHolder = new ViewHolder(); + viewHolder.name = binding.contactDisplayName; + viewHolder.jid = binding.contactJid; + viewHolder.avatar = binding.contactPhoto; + viewHolder.tags = binding.tags; + binding.getRoot().setTag(viewHolder); return viewHolder; } } diff --git a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java index ca6b02284..181bce607 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java @@ -1223,7 +1223,8 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie @Override protected Bitmap doInBackground(Message... params) { - return activity.avatarService().get(params[0], size, isCancelled()); + this.message = params[0]; + return activity.avatarService().get(this.message, size, isCancelled()); } @Override diff --git a/src/main/java/de/pixart/messenger/ui/widget/DisabledActionModeCallback.java b/src/main/java/de/pixart/messenger/ui/widget/DisabledActionModeCallback.java new file mode 100644 index 000000000..5acf3a666 --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/widget/DisabledActionModeCallback.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018, Daniel Gultsch All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * + * 3. Neither the name of the copyright holder nor the names of its contributors + * may be used to endorse or promote products derived from this software without + * specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package de.pixart.messenger.ui.widget; + +import android.view.ActionMode; +import android.view.Menu; +import android.view.MenuItem; + +public class DisabledActionModeCallback implements ActionMode.Callback { + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + return false; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + + } +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/widget/EditMessage.java b/src/main/java/de/pixart/messenger/ui/widget/EditMessage.java index 3b9b50f1c..bb1f0536c 100644 --- a/src/main/java/de/pixart/messenger/ui/widget/EditMessage.java +++ b/src/main/java/de/pixart/messenger/ui/widget/EditMessage.java @@ -5,7 +5,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.support.annotation.Nullable; -import android.support.text.emoji.widget.EmojiEditText; +import android.support.text.emoji.widget.EmojiAppCompatEditText; import android.support.v13.view.inputmethod.EditorInfoCompat; import android.support.v13.view.inputmethod.InputConnectionCompat; import android.support.v13.view.inputmethod.InputContentInfoCompat; @@ -19,15 +19,9 @@ import android.view.inputmethod.InputConnection; import de.pixart.messenger.Config; -public class EditMessage extends EmojiEditText { +public class EditMessage extends EmojiAppCompatEditText { - private static final InputFilter SPAN_FILTER = new InputFilter() { - - @Override - public CharSequence filter(CharSequence source, int start, int end, Spanned dest, int dstart, int dend) { - return source instanceof Spanned ? source.toString() : source; - } - }; + private static final InputFilter SPAN_FILTER = (source, start, end, dest, dstart, dend) -> source instanceof Spanned ? source.toString() : source; protected Handler mTypingHandler = new Handler(); protected KeyboardListener keyboardListener; diff --git a/src/main/java/de/pixart/messenger/ui/widget/Switch.java b/src/main/java/de/pixart/messenger/ui/widget/Switch.java deleted file mode 100644 index 40d8f4c99..000000000 --- a/src/main/java/de/pixart/messenger/ui/widget/Switch.java +++ /dev/null @@ -1,68 +0,0 @@ -package de.pixart.messenger.ui.widget; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.view.ViewConfiguration; - -import com.kyleduo.switchbutton.SwitchButton; - -public class Switch extends SwitchButton { - - private int mTouchSlop; - private int mClickTimeout; - private float mStartX; - private float mStartY; - private OnClickListener mOnClickListener; - - public Switch(Context context) { - super(context); - mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); - mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout(); - } - - public Switch(Context context, AttributeSet attrs) { - super(context, attrs); - mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); - mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout(); - } - - public Switch(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); - mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout(); - } - - @Override - public void setOnClickListener(OnClickListener onClickListener) { - this.mOnClickListener = onClickListener; - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - if (!isEnabled()) { - float deltaX = event.getX() - mStartX; - float deltaY = event.getY() - mStartY; - int action = event.getAction(); - switch (action) { - case MotionEvent.ACTION_DOWN: - mStartX = event.getX(); - mStartY = event.getY(); - break; - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - float time = event.getEventTime() - event.getDownTime(); - if (deltaX < mTouchSlop && deltaY < mTouchSlop && time < mClickTimeout) { - if (mOnClickListener != null) { - this.mOnClickListener.onClick(this); - } - } - break; - default: - break; - } - return true; - } - return super.onTouchEvent(event); - } -} diff --git a/src/main/java/de/pixart/messenger/utils/ExceptionHelper.java b/src/main/java/de/pixart/messenger/utils/ExceptionHelper.java index efec5098b..9aacef24f 100644 --- a/src/main/java/de/pixart/messenger/utils/ExceptionHelper.java +++ b/src/main/java/de/pixart/messenger/utils/ExceptionHelper.java @@ -1,6 +1,6 @@ package de.pixart.messenger.utils; -import android.app.AlertDialog; +import android.support.v7.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; diff --git a/src/main/java/de/pixart/messenger/utils/UIHelper.java b/src/main/java/de/pixart/messenger/utils/UIHelper.java index 451ec3aec..08e1d7dad 100644 --- a/src/main/java/de/pixart/messenger/utils/UIHelper.java +++ b/src/main/java/de/pixart/messenger/utils/UIHelper.java @@ -557,19 +557,4 @@ public class UIHelper { return type; } } - - public static boolean showIconsInPopup(PopupMenu attachFilePopup) { - try { - Field field = attachFilePopup.getClass().getDeclaredField("mPopup"); - field.setAccessible(true); - Object menuPopupHelper = field.get(attachFilePopup); - Class<?> cls = Class.forName("com.android.internal.view.menu.MenuPopupHelper"); - Method method = cls.getDeclaredMethod("setForceShowIcon", new Class[]{boolean.class}); - method.setAccessible(true); - method.invoke(menuPopupHelper, new Object[]{true}); - return true; - } catch (Exception e) { - return false; - } - } } diff --git a/src/main/java/de/pixart/messenger/utils/zxing/IntentIntegrator.java b/src/main/java/de/pixart/messenger/utils/zxing/IntentIntegrator.java index dc5ba8ace..ebb92ae4c 100644 --- a/src/main/java/de/pixart/messenger/utils/zxing/IntentIntegrator.java +++ b/src/main/java/de/pixart/messenger/utils/zxing/IntentIntegrator.java @@ -17,7 +17,7 @@ package de.pixart.messenger.utils.zxing; import android.app.Activity; -import android.app.AlertDialog; +import android.support.v7.app.AlertDialog; import android.app.Fragment; import android.content.ActivityNotFoundException; import android.content.DialogInterface; diff --git a/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java b/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java index 6bd4904bf..1025c5f1d 100644 --- a/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java +++ b/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java @@ -50,8 +50,8 @@ import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509KeyManager; import javax.net.ssl.X509TrustManager; -import de.duenndns.ssl.DomainHostnameVerifier; -import de.duenndns.ssl.MemorizingTrustManager; +import de.pixart.messenger.crypto.DomainHostnameVerifier; +import de.pixart.messenger.services.MemorizingTrustManager; import de.pixart.messenger.Config; import de.pixart.messenger.crypto.XmppDomainVerifier; import de.pixart.messenger.crypto.axolotl.AxolotlService; |