From ae51a379380ed0d7e8a70d5526eddfd8d1212202 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 14 Jul 2019 09:57:03 +0200 Subject: [PATCH 01/19] updated issue template and funding.yml --- .github/FUNDING.yml | 1 + .github/ISSUE_TEMPLATE.md | 12 ++++++------ 2 files changed, 7 insertions(+), 6 deletions(-) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..d2d08b811 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1 @@ +liberapay: inputmice diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index d26e15c2c..b5f52feda 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,12 +1,12 @@ #### General information -* **Version:** eg 1.21.0 -* **Device:** eg Google Nexus 5 -* **Android Version:** eg Android 6.0 Stock or Android 5.1 Cyanogenmod -* **Server name:** eg conversations.im, jabber.at or self hosted +* **Version:** 2.5.3 +* **Device:** Xiaomi Mi A1 +* **Android Version:** Android 9 (stock) +* **Server name:** conversations.im, jabber.at or self hosted * **Server software:** ejabberd 16.04 or prosody 0.10 (if known) -* **Installed server modules:** eg Stream Managment, CSI, MAM -* **Conversations source:** eg PlayStore, PlayStore Beta Channel, F-Droid, self build (latest HEAD) +* **Installed server modules:** Stream Managment, CSI, MAM +* **Conversations source:** PlayStore, PlayStore Beta Channel, F-Droid, self build (latest HEAD) #### Steps to reproduce From 8b2e669da2274c35b8a07bd5e44b136db6952812 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 14 Jul 2019 10:02:03 +0200 Subject: [PATCH 02/19] added paypal to funding --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index d2d08b811..8ade90d85 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,2 @@ liberapay: inputmice +custom: https://paypal.me/ConversationsIM From 31de7690af54da71c2f6ffa3443e445c935a8063 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 14 Jul 2019 10:10:36 +0200 Subject: [PATCH 03/19] added new paypal link to readme --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 93b382caa..f55517c14 100644 --- a/README.md +++ b/README.md @@ -100,10 +100,9 @@ Buying the App from the Play Store will also give you access to our [beta test]( #### I don't have a Google Account but I would still like to make a contribution -I accept donations over PayPal, bank transfer and various crypto currencies. For donations via PayPal you -can use the email address `donate@siacs.eu` or the button below. +I accept donations over PayPal, bank transfer and various crypto currencies. For donations via PayPal use the button below: -[![Donate with PayPal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=CW3SYT3KG5PDL) +[![Donate with PayPal](http://paypal.me/ConversationsIM) **Disclaimer:** I'm not a huge fan of PayPal and their business policies. For larger contributions please get in touch with me beforehand and we can talk From a274ed0ba6ff150c06409c0ec3a2e6d54d34974a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 14 Jul 2019 08:14:28 +0000 Subject: [PATCH 04/19] fixed paypal link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f55517c14..322212ddc 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ Buying the App from the Play Store will also give you access to our [beta test]( I accept donations over PayPal, bank transfer and various crypto currencies. For donations via PayPal use the button below: -[![Donate with PayPal](http://paypal.me/ConversationsIM) +[![Donate with PayPal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.me/ConversationsIM) **Disclaimer:** I'm not a huge fan of PayPal and their business policies. For larger contributions please get in touch with me beforehand and we can talk From 803334e42b4712dea42f36c8a0a89a4aa8b2d025 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 14 Jul 2019 16:38:56 +0200 Subject: [PATCH 05/19] pulled translations from transifex --- src/main/res/values-es/strings.xml | 2 ++ src/main/res/values-pl/strings.xml | 6 ++++-- src/quicksy/res/values-es/strings.xml | 3 ++- src/quicksy/res/values-pl/strings.xml | 3 ++- 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml index c83508a7e..9da8b2d72 100644 --- a/src/main/res/values-es/strings.xml +++ b/src/main/res/values-es/strings.xml @@ -17,6 +17,8 @@ Desbloquear contacto Bloquear dominio Desbloquear dominio + Bloquear participante + Desbloquear participante Gestionar Cuentas Ajustes Compartir con Conversación diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml index 599a3b34a..4a8b94d09 100644 --- a/src/main/res/values-pl/strings.xml +++ b/src/main/res/values-pl/strings.xml @@ -17,6 +17,8 @@ Odblokuj kontakt Zablokuj domenę Odblokuj domenę + Zablokuj użytkownika + Odblokuj użytkownika Zarządzaj kontami Ustawienia Udostępnij w konwersacji @@ -205,8 +207,8 @@ ID klucza OpenPGP Odcisk OMEMO Odcisk v\\OMEMO - Odcisk OMEMO wiadomości - Odcisk v\\OMEMO wiadomości + Odcisk OMEMO tej wiadomości + Odcisk v\\OMEMO tej wiadomości Pozostałe urządzenia Zaufane odciski OMEMO Pobieranie kluczy... diff --git a/src/quicksy/res/values-es/strings.xml b/src/quicksy/res/values-es/strings.xml index bb9c8d206..2c4a1b0b7 100644 --- a/src/quicksy/res/values-es/strings.xml +++ b/src/quicksy/res/values-es/strings.xml @@ -19,4 +19,5 @@ Quicksy necesita acceder al micrófono Esta categoría de notificación se usa para mostrar una notificación permantente indicando que Quicksy está ejecutándose. Foto de perfil en Quicksy - + Quicksy no está disponible en tu país. + diff --git a/src/quicksy/res/values-pl/strings.xml b/src/quicksy/res/values-pl/strings.xml index b08a73a90..9b9961c6e 100644 --- a/src/quicksy/res/values-pl/strings.xml +++ b/src/quicksy/res/values-pl/strings.xml @@ -19,4 +19,5 @@ Quicksy potrzebuje dostępu do mikrofonu. Ta kategoria powiadomień jest używana do wyświetlania ciągłego powiadomienia o tym, że Quicksy działa. Obrazek profilowy Quicksy - + Quicksy nie jest dostępne w twoim kraju + From 719b35e4315a34d29767919bf8d5fc43f6a7b96c Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 14 Jul 2019 16:39:09 +0200 Subject: [PATCH 06/19] prefer attachment in share intent if there is one --- src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java index 9d6385db8..ce64af856 100644 --- a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java @@ -131,10 +131,11 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer if (Intent.ACTION_SEND.equals(action)) { final String text = intent.getStringExtra(Intent.EXTRA_TEXT); final Uri uri = intent.getParcelableExtra(Intent.EXTRA_STREAM); + if (data != null && "geo".equals(data.getScheme())) { this.share.uris.clear(); this.share.uris.add(data); - } else if (type != null && uri != null && (text == null || !type.equals("text/plain"))) { + } else if (type != null && uri != null) { this.share.uris.clear(); this.share.uris.add(uri); } else { From bc3e5f1dd4b27953d41ec9afad2e3e091c7a6b7d Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 14 Jul 2019 17:58:28 +0200 Subject: [PATCH 07/19] muc message corrections only compare bare jid true counterpart during live messages we only store the bare real jid; on muc catch up we might get the full jid for that reason we only compare bare jids --- src/main/java/eu/siacs/conversations/parser/MessageParser.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 5adaffe34..33179b851 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -543,7 +543,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece final boolean fingerprintsMatch = replacedMessage.getFingerprint() == null || replacedMessage.getFingerprint().equals(message.getFingerprint()); final boolean trueCountersMatch = replacedMessage.getTrueCounterpart() != null - && replacedMessage.getTrueCounterpart().equals(message.getTrueCounterpart()); + && message.getTrueCounterpart() != null + && replacedMessage.getTrueCounterpart().asBareJid().equals(message.getTrueCounterpart().asBareJid()); final boolean mucUserMatches = query == null && replacedMessage.sameMucUser(message); //can not be checked when using mam final boolean duplicate = conversation.hasDuplicateMessage(message); if (fingerprintsMatch && (trueCountersMatch || !conversationMultiMode || mucUserMatches) && !duplicate) { From 97598c63dc979a5bf692562ef084a75132a94b7d Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 15 Jul 2019 19:09:48 +0200 Subject: [PATCH 08/19] split on first dot when using domain instead of black listed local part --- .../siacs/conversations/utils/JidHelper.java | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/utils/JidHelper.java b/src/main/java/eu/siacs/conversations/utils/JidHelper.java index 562e179ab..8a1510fd2 100644 --- a/src/main/java/eu/siacs/conversations/utils/JidHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/JidHelper.java @@ -39,24 +39,24 @@ import rocks.xmpp.addr.Jid; public class JidHelper { - private static List LOCALPART_BLACKLIST = Arrays.asList("xmpp","jabber","me"); + private static List LOCAL_PART_BLACKLIST = Arrays.asList("xmpp", "jabber", "me"); - public static String localPartOrFallback(Jid jid) { - if (LOCALPART_BLACKLIST.contains(jid.getLocal().toLowerCase(Locale.ENGLISH))) { - final String domain = jid.getDomain(); - final int index = domain.lastIndexOf('.'); - return index > 1 ? domain.substring(0,index) : domain; - } else { - return jid.getLocal(); - } - } + public static String localPartOrFallback(Jid jid) { + if (LOCAL_PART_BLACKLIST.contains(jid.getLocal().toLowerCase(Locale.ENGLISH))) { + final String domain = jid.getDomain(); + final int index = domain.indexOf('.'); + return index > 1 ? domain.substring(0, index) : domain; + } else { + return jid.getLocal(); + } + } - public static Jid parseOrFallbackToInvalid(String jid) { - try { - return Jid.of(jid); - } catch (IllegalArgumentException e) { - return InvalidJid.of(jid, true); - } - } + public static Jid parseOrFallbackToInvalid(String jid) { + try { + return Jid.of(jid); + } catch (IllegalArgumentException e) { + return InvalidJid.of(jid, true); + } + } } From b68851b719d89f2f6b7746189d34ccf5d2f295a6 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 15 Jul 2019 19:11:00 +0200 Subject: [PATCH 09/19] create share button in backup done notification --- .../services/ExportBackupService.java | 134 +++++++++++------- .../ui/adapter/ConversationAdapter.java | 1 + .../ui/adapter/MediaAdapter.java | 3 + .../siacs/conversations/utils/MimeUtils.java | 2 + .../siacs/conversations/utils/UIHelper.java | 5 + .../drawable-hdpi/ic_backup_black_48dp.png | Bin 0 -> 561 bytes .../drawable-hdpi/ic_backup_white_48dp.png | Bin 0 -> 589 bytes .../drawable-mdpi/ic_backup_black_48dp.png | Bin 0 -> 386 bytes .../drawable-mdpi/ic_backup_white_48dp.png | Bin 0 -> 405 bytes .../drawable-xhdpi/ic_backup_black_48dp.png | Bin 0 -> 733 bytes .../drawable-xhdpi/ic_backup_white_48dp.png | Bin 0 -> 770 bytes .../drawable-xxhdpi/ic_backup_black_48dp.png | Bin 0 -> 1116 bytes .../drawable-xxhdpi/ic_backup_white_48dp.png | Bin 0 -> 1177 bytes .../drawable-xxxhdpi/ic_backup_black_48dp.png | Bin 0 -> 1465 bytes .../drawable-xxxhdpi/ic_backup_white_48dp.png | Bin 0 -> 1562 bytes src/main/res/values/attrs.xml | 1 + src/main/res/values/strings.xml | 3 + src/main/res/values/themes.xml | 2 + 18 files changed, 98 insertions(+), 53 deletions(-) create mode 100644 src/main/res/drawable-hdpi/ic_backup_black_48dp.png create mode 100644 src/main/res/drawable-hdpi/ic_backup_white_48dp.png create mode 100644 src/main/res/drawable-mdpi/ic_backup_black_48dp.png create mode 100644 src/main/res/drawable-mdpi/ic_backup_white_48dp.png create mode 100644 src/main/res/drawable-xhdpi/ic_backup_black_48dp.png create mode 100644 src/main/res/drawable-xhdpi/ic_backup_white_48dp.png create mode 100644 src/main/res/drawable-xxhdpi/ic_backup_black_48dp.png create mode 100644 src/main/res/drawable-xxhdpi/ic_backup_white_48dp.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_backup_black_48dp.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_backup_white_48dp.png diff --git a/src/main/java/eu/siacs/conversations/services/ExportBackupService.java b/src/main/java/eu/siacs/conversations/services/ExportBackupService.java index d715ea539..7b034a255 100644 --- a/src/main/java/eu/siacs/conversations/services/ExportBackupService.java +++ b/src/main/java/eu/siacs/conversations/services/ExportBackupService.java @@ -21,7 +21,9 @@ import java.io.PrintWriter; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.security.spec.InvalidKeySpecException; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; import java.util.zip.GZIPOutputStream; @@ -50,6 +52,8 @@ public class ExportBackupService extends Service { public static final String CIPHERMODE = "AES/GCM/NoPadding"; public static final String PROVIDER = "BC"; + public static final String MIME_TYPE = "application/vnd.conversations.backup"; + private static final int NOTIFICATION_ID = 19; private static final int PAGE_SIZE = 20; private static AtomicBoolean running = new AtomicBoolean(false); @@ -213,11 +217,19 @@ public class ExportBackupService extends Service { public int onStartCommand(Intent intent, int flags, int startId) { if (running.compareAndSet(false, true)) { new Thread(() -> { - final boolean success = export(); + boolean success; + List files; + try { + files = export(); + success = true; + } catch (Exception e) { + success = false; + files = Collections.emptyList(); + } stopForeground(true); running.set(false); if (success) { - notifySuccess(); + notifySuccess(files); } stopSelf(); }).start(); @@ -250,81 +262,97 @@ public class ExportBackupService extends Service { } } - private boolean export() { + private List export() throws Exception { NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup"); mBuilder.setContentTitle(getString(R.string.notification_create_backup_title)) .setSmallIcon(R.drawable.ic_archive_white_24dp) .setProgress(1, 0, false); startForeground(NOTIFICATION_ID, mBuilder.build()); - try { - int count = 0; - final int max = this.mAccounts.size(); - final SecureRandom secureRandom = new SecureRandom(); - for (Account account : this.mAccounts) { - final byte[] IV = new byte[12]; - final byte[] salt = new byte[16]; - secureRandom.nextBytes(IV); - secureRandom.nextBytes(salt); - final BackupFileHeader backupFileHeader = new BackupFileHeader(getString(R.string.app_name), account.getJid(), System.currentTimeMillis(), IV, salt); - final Progress progress = new Progress(mBuilder, max, count); - final File file = new File(FileBackend.getBackupDirectory(this) + account.getJid().asBareJid().toEscapedString() + ".ceb"); - if (file.getParentFile().mkdirs()) { - Log.d(Config.LOGTAG, "created backup directory " + file.getParentFile().getAbsolutePath()); - } - final FileOutputStream fileOutputStream = new FileOutputStream(file); - final DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream); - backupFileHeader.write(dataOutputStream); - dataOutputStream.flush(); - - final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); - byte[] key = getKey(account.getPassword(), salt); - Log.d(Config.LOGTAG, backupFileHeader.toString()); - SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); - IvParameterSpec ivSpec = new IvParameterSpec(IV); - cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); - CipherOutputStream cipherOutputStream = new CipherOutputStream(fileOutputStream, cipher); - - GZIPOutputStream gzipOutputStream = new GZIPOutputStream(cipherOutputStream); - PrintWriter writer = new PrintWriter(gzipOutputStream); - SQLiteDatabase db = this.mDatabaseBackend.getReadableDatabase(); - final String uuid = account.getUuid(); - accountExport(db, uuid, writer); - simpleExport(db, Conversation.TABLENAME, Conversation.ACCOUNT, uuid, writer); - messageExport(db, uuid, writer, progress); - for (String table : Arrays.asList(SQLiteAxolotlStore.PREKEY_TABLENAME, SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, SQLiteAxolotlStore.SESSION_TABLENAME, SQLiteAxolotlStore.IDENTITIES_TABLENAME)) { - simpleExport(db, table, SQLiteAxolotlStore.ACCOUNT, uuid, writer); - } - writer.flush(); - writer.close(); - Log.d(Config.LOGTAG, "written backup to " + file.getAbsoluteFile()); - count++; + int count = 0; + final int max = this.mAccounts.size(); + final SecureRandom secureRandom = new SecureRandom(); + final List files = new ArrayList<>(); + for (Account account : this.mAccounts) { + final byte[] IV = new byte[12]; + final byte[] salt = new byte[16]; + secureRandom.nextBytes(IV); + secureRandom.nextBytes(salt); + final BackupFileHeader backupFileHeader = new BackupFileHeader(getString(R.string.app_name), account.getJid(), System.currentTimeMillis(), IV, salt); + final Progress progress = new Progress(mBuilder, max, count); + final File file = new File(FileBackend.getBackupDirectory(this) + account.getJid().asBareJid().toEscapedString() + ".ceb"); + files.add(file); + if (file.getParentFile().mkdirs()) { + Log.d(Config.LOGTAG, "created backup directory " + file.getParentFile().getAbsolutePath()); } - return true; - } catch (Exception e) { - Log.d(Config.LOGTAG, "unable to create backup ", e); - return false; + final FileOutputStream fileOutputStream = new FileOutputStream(file); + final DataOutputStream dataOutputStream = new DataOutputStream(fileOutputStream); + backupFileHeader.write(dataOutputStream); + dataOutputStream.flush(); + + final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); + byte[] key = getKey(account.getPassword(), salt); + Log.d(Config.LOGTAG, backupFileHeader.toString()); + SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); + IvParameterSpec ivSpec = new IvParameterSpec(IV); + cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); + CipherOutputStream cipherOutputStream = new CipherOutputStream(fileOutputStream, cipher); + + GZIPOutputStream gzipOutputStream = new GZIPOutputStream(cipherOutputStream); + PrintWriter writer = new PrintWriter(gzipOutputStream); + SQLiteDatabase db = this.mDatabaseBackend.getReadableDatabase(); + final String uuid = account.getUuid(); + accountExport(db, uuid, writer); + simpleExport(db, Conversation.TABLENAME, Conversation.ACCOUNT, uuid, writer); + messageExport(db, uuid, writer, progress); + for (String table : Arrays.asList(SQLiteAxolotlStore.PREKEY_TABLENAME, SQLiteAxolotlStore.SIGNED_PREKEY_TABLENAME, SQLiteAxolotlStore.SESSION_TABLENAME, SQLiteAxolotlStore.IDENTITIES_TABLENAME)) { + simpleExport(db, table, SQLiteAxolotlStore.ACCOUNT, uuid, writer); + } + writer.flush(); + writer.close(); + Log.d(Config.LOGTAG, "written backup to " + file.getAbsoluteFile()); + count++; } + return files; } - private void notifySuccess() { + private void notifySuccess(List files) { final String path = FileBackend.getBackupDirectory(this); - PendingIntent pendingIntent = null; + PendingIntent openFolderIntent = null; for (Intent intent : getPossibleFileOpenIntents(this, path)) { if (intent.resolveActivityInfo(getPackageManager(), 0) != null) { - pendingIntent = PendingIntent.getActivity(this, 189, intent, PendingIntent.FLAG_UPDATE_CURRENT); + openFolderIntent = PendingIntent.getActivity(this, 189, intent, PendingIntent.FLAG_UPDATE_CURRENT); break; } } + PendingIntent shareFilesIntent = null; + if (files.size() > 0) { + final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE); + ArrayList uris = new ArrayList<>(); + for(File file : files) { + uris.add(FileBackend.getUriForFile(this, file)); + } + intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uris); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + intent.setType(MIME_TYPE); + final Intent chooser = Intent.createChooser(intent, getString(R.string.share_backup_files)); + shareFilesIntent = PendingIntent.getActivity(this,190, chooser, PendingIntent.FLAG_UPDATE_CURRENT); + } + NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup"); mBuilder.setContentTitle(getString(R.string.notification_backup_created_title)) .setContentText(getString(R.string.notification_backup_created_subtitle, path)) .setStyle(new NotificationCompat.BigTextStyle().bigText(getString(R.string.notification_backup_created_subtitle, FileBackend.getBackupDirectory(this)))) .setAutoCancel(true) - .setContentIntent(pendingIntent) + .setContentIntent(openFolderIntent) .setSmallIcon(R.drawable.ic_archive_white_24dp); + + if (shareFilesIntent != null) { + mBuilder.addAction(R.drawable.ic_share_white_24dp, getString(R.string.share_backup_files), shareFilesIntent); + } + notificationManager.notify(NOTIFICATION_ID, mBuilder.build()); } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java index ad5ddce89..3ea451653 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/ConversationAdapter.java @@ -94,6 +94,7 @@ public class ConversationAdapter extends RecyclerView.Adapterdc5cH-b0=D=O1`+&;6cz{l^s*6%`c~ZPDYR2j2K$*^=*G z8+XBeo7K2x2aQ`4R$1AYUX5x}qkiv&h`n=c{ZYHU5X5iBF6&mwSC@(( zQ04C0AaR>a2pRTBYuc-NC}iAy&60IpNU$ps)h8s{0SVgbTQbms4w)Mf5^h-4?Qp`V z8NY;x4H^_8wq&O@*QC#s5I=(uNxEu{b$KOJ!HFgPv{7_zMue(4v82Ny+h#f=Cyq24 zd)~7NI5DJ;B5L(45hsQ;Ys@)&G$eT2L~!CS?yXZ=RHiF2C!Q31&{gr?$#Cc?NWpm} zD>|B$lYn$%CFx0aP6E;;fX+0W1f(9|WExHa(kJb3I~6AZ=?pv*BpU@ zZWeO|8p<1JGH;LUGc&mYmARES(D7V>=IPAamzu}%~aPY;Jx!nt**mE;azb zwH$Efv;)xMY287+{;0#tx^gb7sKZpcNMDmi4LCYS{S z+C{cqzni9gQBKS#n>T4lwPNf literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-hdpi/ic_backup_white_48dp.png b/src/main/res/drawable-hdpi/ic_backup_white_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..3ff57ad3e897a332dc77086a5d3fdb9ed0c40b41 GIT binary patch literal 589 zcmV-T0Bp}VWfd*+9a9l|5T|oUj(Q*`|2D6-HH+>yJ`n#hb#l7Yv#lL|nHXH>B-ck7z zl-E6UGZLI5A_j59nxi0HBZ@(f>^Tb3#owSIdybMc@(XlA$+0JWpo9oF)f{`$X+(I6 zzD@vbO$x$ik3cXita=5S@C~%!+mU?~>Ry3b!VTX*$Gifq2t&SIYT=<@&t5p}*9W5z z(eUQ0BQFTTUi@f0{*ix$>|3T3gNi&R>#9f$63RR$%O=K5T(~PiE~6R#|+6jGNjAsVV3UDffiFJSYVqwI>wnQRm9*9e~=>} zLK3V37!xhh$ye_oiut6(B1Se5^}IPz!$4U*pPUGJ%cR-It_$ScO9rW4K!e$ z$n7J;mD_I$S$c>Qn`8iB%~7DpHyaM-5Bb3=A0wTwF;0v*E{R5T`yvLiY;nsAq1A9m ggMbnlJRZ*gUuq}bb_}IlRR91007*qoM6N<$f_R^x2LJ#7 literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-mdpi/ic_backup_white_48dp.png b/src/main/res/drawable-mdpi/ic_backup_white_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..a9602d11b8d4e33f91367f9676eebce86d88c117 GIT binary patch literal 405 zcmV;G0c!qYU6oB!gw#7w6AHZ6C8lfO9C{)|G5Tytb2Nyx8_-E0{MU0E9)CbTPaFR+9g|>FG zON!-l$YQ|cp2S{o$amIoesFUk5nX9%X-u=i8ISb%Vn~+#XgXyo36S9yZ8t$e2V4-Rlej>QxUI$k=IIl+4l^2Hi5>3g5tf5P zSi2^mfXj%e90Um^I%K1O8Urw|k0h4Efi2LiHxP|5psHJMBs`wDq(mkN6x4@fCj6c$ z{sdh0dOZOLO!z=Sb8`|Ahdxj@uTM;r5TR*YpPXou72eS$!9iC=7+M8f+rW$PW)<*+ zrfr~Z6=(^IjO@N-;o9ys74i(QQ=6P12pia`j0pa0GX9Q&pUibW8D|Y9gMhHW6{9ho zeL3v6CC@f@yyB50j}|T!a!3%cI4O_<{D0sZ6}?4(_}0Fh00000NkvXXu0mjfY7nTZ literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xhdpi/ic_backup_black_48dp.png b/src/main/res/drawable-xhdpi/ic_backup_black_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..248289e97e9b984f3c8441d9690d26fe75659755 GIT binary patch literal 733 zcmV<30wVp1P)Ds^(9wc2%hE=pP1M4*1VKb(xCjD8NL;uJ0+HyG*-S8?s}|OXZmMl0iiL}U z0ud~Vp$}tP)*^8cGL_Oep)$_fPBcH~o^$5j1LyZ&3-5ECnTz8%j^j9v<2a5s6Vw`6$0d|yIyd{Thu^bazqmIC7;yKI64wvH@jReLyl8A@B;S9d3 z<0+!BA!_hZ2Xlzxe$$38%6W)rT0dnJ*?uMvjT@taB09tuMDr#&SiGm7LJaH!6_`gk z6NsUWQ;Knq5Q9r#SO;QwmoVrEbH)SGRAbC2g82<%NF!3DokFWRI_PJNS)|~_iF^2~ z*ZN*qH~ENoUL3$v!f74PeSp7+^7-BpMo?159p(}B^F1fzsKK*(CJ{a7DRE<87cvwUG01y6M6xlPvv}H6Beiec!g-)c0}X4 z0lrxr@SPCMS{xvpge9v3qP7Q&z%Q!VQQ^*dEYsbwC;p+x{hn_Z9~X z16;NHMFv%5tqo`ZfcusPOc4TrTIQ_`=wU?^v2F`QSP|y4bpd^>ae@qD(PpURKP@a< z6wt~BuGsqVS(RYLe_@moHr33Zz@NXHEgfev&>x->wiV_!^MU;3X=TSE^pXwKYb!w| z1*)QpBzeT=P0>RH&#GzT9xwPxnnk2YTO>`2VWKn;VjahE9LI4S$8ns0bEkQc(oxfv P00000NkvXXu0mjf7#&5* literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xhdpi/ic_backup_white_48dp.png b/src/main/res/drawable-xhdpi/ic_backup_white_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..2180f73e8d7d300cddc7ae2c2a071ee58ddd4260 GIT binary patch literal 770 zcmV+d1O5DoP) z8s{2&w+58)!OVOi0;Gttdvidqnfamwn4@K5K-kdNmONpNK0^NjMg-jC>n z3(S`!j1lqzt_sYTCfxP{VgmE!3FrO*8bs#H6Bek-0Xl@{%M%82fFYsz@`McaD8&nm zw(V4t^`R7BECzffq)1u}fMbdks{uW>111#Ttp>#H{t(0eisx1XmKB4x13Ih*Boqg1 z{}3zQS_~LhC@$OmMW(1BZ7rZFchkF;0_F(iZrZ?-m4N=g54L)+uMA=Dqs5Qd*3kNg zyGKc3P2PX%y=OD4*i+j^2OO7d`t|Vwyo6PP-y|iQ0TiwL4E+3ETOYF?VV2-FM9KCC z-DTWliQu(B+sgz2{2y@T40?tFU^V~%rO?&kWQ>vx^&`}+I`Z}@Rt7r($QF)q-}cf7{|8mOj_*eRrj19b2& zvVcu+niQKTAO;H9%DW6AOIRPr*+L$>iM@0q%iM1?v6{TIhQmxE0d0~uxtYANiZ}QN z33!)Dv6?*4z(qu9gX|#E*6|ghyf3(gh^l8C(H|2$LgbWi0?FatrkDt+;4G5kouz_^ zxPu-fg#F2_L_jr{kdW5TJvi=ehL8|9%w0HaJ%1x1?;^M1s3d?IHCb@boDr(oPNa7U7(E6y@oivR|rWF z8`;GX+L%HDT#6JD;3i0tV{6$%8bcmcOrIBx#K@hz>fhQWJ9DUrteg+Uk zJC8!5-6$(!4`Yaa9;HNopsJ4FkzD3cNVE|p367D;bsmL8M^JGmKO?!%qmZbN0s>dd zXy|zWQf?lyr$~pFMY&MY9AouG*iVQHM^TfA(4>b!^IrAtz_KJqmW2QbCfa` zpqOs=^Qa^edf38sJ@0-V#Y8jg$JdRFxSvNgk?;`-JiQvJJX(pqri`pg=-1B^iA0^O zB#S5Y^F$`m#}qIh-|FXyP@;p(Q>lMb5vfEO9_AX)>F0@9q5&$Hqg_8wB>*HCut0t{ z8}O0^%CaVAtzyIi&0CrzbL_Q1{(nqR#4m_#&hBTP2N2urI>_RU=ln*J6Dms zSejO9P*BOUv@(W7c2jiFL=shn+(#pa_>`aMWytZ@%4vq^rHdB!QAdJn7-Nhv#u#Ia iF~%5Uj4{R-WBvo3>(GofhKo-C0000& zMxDi~6k)=!I+;?Z7Ym|nOt((Q@UEK(3Zht*YGGn5vJON+=Fe&=gKlkIn>J}dn{3JB zMJVlN&RKt)Z_b4OC+M z8-pKHNfY~t@h^)h$tj|2rlK5xD%i%mjIo@CImXsf2D*X046xFEqn)*-2eghu%-~fs zyvdEF1GI)W_y@0>Cd%4U0BYp|wi;t+aRaUAD{S`#Hy0~V3keEQl81{AsG8#xvbPBo z6HtUR6uL7+iUsI42JxdmxuqC@8n}pGjc}KApgS4I&nCD-GSEi;#_uk0t5l#ce<7hk z)=31ap&u#rQ6&%PIFdRl4QMCQdQ1|~dXh+Mk~%p+Um~$?DL}1AtwjP*h!LcAo?8Eb zUPE%P_ytr&BfB_E53_h#lqg=7BwT2qo7h7S)7U7A!FWt`uy8;%wDAoY0E;|c6HO8- z1kiQtV-&zPj|HOLwt=eH!xY%(u|)KTU7%)uheGmLAZoM)6yzvbpLo0^I&25%c76sQ zdAuVUrowxm?M(W`V}YpAYoMn{`^RH}sKZ;JHnM*5SRm^066gss{_J$)&~`5Q z&trk;H1G2|Eo@jB&_?32cq}~h$2h@51eXI8$)AFv!+x13fRF$0AXNeeOWl6PM3pl?Xl}=nV9#JRTp2 zzNR)mP&FeGcmxpju{s~nlLC085PeL=|AD?0z$1s~z|}x?WCZZYAE%BY;OH(dHb`TlkY%;uOA5azLLc zkVBUUV+>~%$g$lR!>|H57Q+~(6v(kzf`+sLxpcn*x%aXmu0SrGFq~5$mrmSEQp)f&t|`za1#)TJu!@8NIW}Z4e6B!_^}D~7SD>!k{!T`L9O}sZ_$dW) z=z+QKS`{c?v#g%`HgOb(^dDxqc}aot)IJZihPVPglzThe7wR4*Lpj%Z?Ul@3 zWlYpd;MzcjM@UhQeNSldYvGA9(ymnn_Xbkk(x$XgxH(#;gb+bl8K z3EMprSjj!Kaga~>i6O>GA-NRe4AD;)9W)d4@I;r65-5-YDUbpwkOC=?0_iA_0x6IJ rDUbpwkOJu_kOC=?0x6IJmBjo9--gpSw0n+n00000NkvXXu0mjf(p?MD literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xxxhdpi/ic_backup_black_48dp.png b/src/main/res/drawable-xxxhdpi/ic_backup_black_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..33d75230cfe7189fb64a79fb64bcbfa66a2ba60c GIT binary patch literal 1465 zcmV;q1xEUbP)G0000GmNklqr?$l|U;qFB00000000000HL#MFTYV0Y#hhGLuqMvr6nyjP(Xu;nsj4O zDdA!u1p1QNN)ZVJL_nEntP(dajGHmCQGp2P0ul&OOrXGmU?Mj!1Oy_N+5%AnHZ3I) zEoG=6IBhwPo02r8lul>nJm>O!KmWn=Tg;i0lgAXIe8?8|bCxFl;s%|x6X!C&ahh-0 z%=;{)lme`@mBq8X%N`n$Bs8#%YQ~erfU^pDl|5WXQgDM^EFuppsfH2dARS1WB>0Bc zcnGT>jb|e_kPP^n&nUs_qjHWS8TLJMuBc4UXU-3St1RPB0S>6`_K?v?EGlbT1aJzhv1g<2<4$2H^Sx z4o{%oAfhxQ`sY`f;B%ClMlIrn^D9kolmDUC!_*+&IKScq3s9<(KfQ8()d_Z^&`>ra zUOB(=1h*N6%Z;VZL+96@U@@*Xfj>NTe*Fn{;9~Q*<+1bYPjCg-T1ck{&#ynhlep4+ zkDgzDf+#LDmky7fUw?vaWW0&o_U!rfCun0kA5+cqj3OhA@y(Nu{zGXKHN3@G(%4W= z`}h26ujx+mUb<%39Dws{Odxc!i)o}-WdP2vG(neB%woWe;7S0_uRVcqkf#|?H32!l z{sedUgj|wrMu5(*KY{QolSraL)CTPQ`V(~U4*hOf0MD;Kfv}Za`q2dj0bUdV+e2=%ZQs=MVe@$I0cs?bSbj2qvf@La!C*f8!aF30Bc- zEA-DFk_jAU&`XE(&mWozE>l2{M$oB${t!*DgC5P(KYz$3m;v2er+@yCO;Ar3-8y6E zcYT64;cj`{vGcn=!6mZkYKqGFZGu zB^%%u8$eAtK(h^?W-UP622gVbpxp*gbBEzJfUYuVwE+~p4NW$Hq787)22ivQj@tl= z&caR`KvAQ;jMH@$me>G_5>Se0vH|jNYydSa0QEM2ng)PRZ2&cO0CQ{rHTwbb>9hfq ztOtN2Hh_|90C?R7P%;w$hV!otpunM!u6Ei03TokQ6*ho^HE_2G=WPJjmD8=4Z2*_W z$)#Ia{B8rdY7;$*+5oP4jvfuB-Ue{Z1tQ$jR2;;(B1*4qMT`rY$)VSZxQ1k$S;>8y zi)5I&KrVf>9?2*(n?B3oB$6>^HxKMFt|A#>S{Xw>n#fJWWI!eTY&PwPNwYO1(PA9L zq|srrA-M_K5tAnMG=JSter&p1fvIMlL+a)y9@!+3^8e86^& zQbz+VB#?kf&_V-s>}NgI%%qU6{%a2o1ONa400jA44{~({000000000000004q?P3I T@}!)D00000NkvXXu0mjfg^!Rs literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xxxhdpi/ic_backup_white_48dp.png b/src/main/res/drawable-xxxhdpi/ic_backup_white_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..19e40517da00c23651574c1f37341012acf09568 GIT binary patch literal 1562 zcmZuxc{J2}6#xFl*v5<{BWq(wA!TMFjjb6{7>z-^vOUuaDOt1T$qywmO+t0-V@k`z zvujghD6&N;Noh*fh02oIHJ2x>m z|4JF87*}53VgVre$@zjk!b!1~HBuctulvY9#N5Qy^l;bySG2SJsZ$wO89P+MW|C8M12~7Z(``qix8~DE zE9hmmngz^%AOn6ftU${&lw#zOx54ZcZJd+3Z~nMI>p$I;zb&iswu;MoZa(X^Ch z9)pdz6|+A*GRv&rqp&KzNLmG-r`aVHDma+*UHt&6Z{%7e_Wl|>54Et7>B2W`n$g>a z>JwcuXAdRzBGD+Sj-LQOa0GjP3!WyI?Hk}42jM2bxhcitEi%N5n}u$yOW}}z?Pgv> zcJeVujZYH3RbZhds7jcX1}CiK7AA@$^f4JrMZ4H&@nSP;Ify0)BeTJ_Sv(?Fx&8X* zTqK5xia0$JwjC+UNmpxB8SCHx^c|e$V>=r~P)km(Oms>Uf#8~l*BT|7SaUrM>H;ON z>L*`W^{N736I4|RoMdToAxMKS7BEkh380r0XQy7NaEr7K8A4Q`a+kG zi+rpHr0O8yZXS^zk`{*{=4)v85E#|X-f)EQTlwm^Y-55fvA}y@fnTxUQY=^sTK*(l zL^9$ei0vt>0W#d*K{vODy(%fw_()-4=lzg8KbUhN=!x>1&{}l#2 z_=SG3K)xiqXh-v);3a_yc*K1wFytQoz5et*5czBn;z63G7i(lfGE>L^8 zEPZs7a8**)`D!c$ekbT9&|I8s!oWZ(g-7N`)Xrn7^`n-Y6y22^(OLQ;&E7u63I}wx zX`y&K@*z4h+@M0Ne70BVHxu8rTJiw@XECU2wHAxP4MYWIS%^S3_(w^TZ!RS`FTC-E zwZ2V>Pw%5!C&%QzB-k#TQf<~17EMpCHq`k7BP{i9M-dni6Q&60iz#q7DXm0>TH(ej zb%maJT_!!lQY%c<_Ln2lqc4`C@ZK4vo)9vjy^jM@D)$y17SC5_8^O#I7)va*`mE?g z;prn7ITdR4gHp9uSiH17^z2l&$hRIbsE!U6TWO>oyNyV1_{e-w<_n$oIx+QND+=#k zRysE70!nX3CoXSjODew5`PdgP_T!vll`eIP*&`OxEwtAYdD4cSZ~;zr>tjo*6weU8 z6EeG6!@i4%dw0efl5#5t#nH(Q4A82lWg2#A`?~+|W>dl`JtGno8VaX`3_qcGo6FrF zFwRO01FaVC=>^Ht7<^_<5YAe{-y!Nsq)KdmzWTNIlV8I=AM}c^RUmhBRCax+q>anw zNRor5XNh_DK$K!Vx2)VN@^DhyOnfM}?B-e2+RrR+5(q}h&#lQX377Gce_g@NwJ~nGTeOJzbaG-zXM{|{M4Kw<2R%Q@Wo!JbW7BS5{kSoC z`UjGs?*b={+)ULGzh&5F{Y}o>IyoIkMiB!s{spSmuFe1e literal 0 HcmV?d00001 diff --git a/src/main/res/values/attrs.xml b/src/main/res/values/attrs.xml index 2c2acba97..e2f7ddf76 100644 --- a/src/main/res/values/attrs.xml +++ b/src/main/res/values/attrs.xml @@ -63,6 +63,7 @@ + diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 419928a80..90866434e 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -868,4 +868,7 @@ This looks like a domain address Add anyway This looks like a channel address + Share backup files + Conversations backup + Event diff --git a/src/main/res/values/themes.xml b/src/main/res/values/themes.xml index e7bc3cd31..f4c870603 100644 --- a/src/main/res/values/themes.xml +++ b/src/main/res/values/themes.xml @@ -74,6 +74,7 @@ @drawable/ic_event_black_48dp @drawable/ic_archive_black_48dp @drawable/ic_book_black_48dp + @drawable/ic_backup_black_48dp @drawable/ic_help_black_48dp @drawable/ic_group_add_white_24dp @@ -187,6 +188,7 @@ @drawable/ic_event_white_48dp @drawable/ic_archive_white_48dp @drawable/ic_book_white_48dp + @drawable/ic_backup_white_48dp @drawable/ic_help_white_48dp @drawable/ic_group_add_white_24dp From 603e1b35a58c5395623aa6a8a5d3876c272b2d15 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 16 Jul 2019 16:49:47 +0200 Subject: [PATCH 10/19] allow backup to be restored from selected file --- .../services/ImportBackupService.java | 89 +++++++++++++----- .../ui/ImportBackupActivity.java | 89 ++++++++++++++++-- .../ic_cloud_download_white_24dp.png | Bin 0 -> 353 bytes .../ic_cloud_download_white_24dp.png | Bin 0 -> 242 bytes .../ic_cloud_download_white_24dp.png | Bin 0 -> 417 bytes .../ic_cloud_download_white_24dp.png | Bin 0 -> 610 bytes .../ic_cloud_download_white_24dp.png | Bin 0 -> 789 bytes src/main/res/menu/import_backup.xml | 11 +++ src/main/res/values/attrs.xml | 3 + src/main/res/values/strings.xml | 3 + src/main/res/values/themes.xml | 2 + 11 files changed, 164 insertions(+), 33 deletions(-) create mode 100644 src/main/res/drawable-hdpi/ic_cloud_download_white_24dp.png create mode 100644 src/main/res/drawable-mdpi/ic_cloud_download_white_24dp.png create mode 100644 src/main/res/drawable-xhdpi/ic_cloud_download_white_24dp.png create mode 100644 src/main/res/drawable-xxhdpi/ic_cloud_download_white_24dp.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_cloud_download_white_24dp.png create mode 100644 src/main/res/menu/import_backup.xml diff --git a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java index 9892e6492..b982e1be8 100644 --- a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java +++ b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java @@ -7,6 +7,7 @@ import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; +import android.net.Uri; import android.os.Binder; import android.os.IBinder; import android.support.v4.app.NotificationCompat; @@ -16,18 +17,20 @@ import java.io.BufferedReader; import java.io.DataInputStream; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; -import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.atomic.AtomicBoolean; import java.util.zip.GZIPInputStream; +import java.util.zip.ZipException; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; @@ -81,14 +84,22 @@ public class ImportBackupService extends Service { return START_NOT_STICKY; } final String password = intent.getStringExtra("password"); - final String file = intent.getStringExtra("file"); - if (password == null || file == null) { + final Uri data = intent.getData(); + final Uri uri; + if (data == null) { + final String file = intent.getStringExtra("file"); + uri = file == null ? null : Uri.fromFile(new File(file)); + } else { + uri = data; + } + + if (password == null || uri == null) { return START_NOT_STICKY; } if (running.compareAndSet(false, true)) { executor.execute(() -> { startForegroundService(); - final boolean success = importBackup(new File(file), password); + final boolean success = importBackup(uri, password); stopForeground(true); running.set(false); if (success) { @@ -122,7 +133,7 @@ public class ImportBackupService extends Service { try { final BackupFile backupFile = BackupFile.read(file); if (accounts.contains(backupFile.getHeader().getJid())) { - Log.d(Config.LOGTAG,"skipping backup for "+backupFile.getHeader().getJid()); + Log.d(Config.LOGTAG, "skipping backup for " + backupFile.getHeader().getJid()); } else { backupFiles.add(backupFile); } @@ -145,21 +156,43 @@ public class ImportBackupService extends Service { startForeground(NOTIFICATION_ID, mBuilder.build()); } - private boolean importBackup(File file, String password) { - Log.d(Config.LOGTAG, "importing backup from file " + file.getAbsolutePath()); + private boolean importBackup(Uri uri, String password) { + Log.d(Config.LOGTAG, "importing backup from " + uri); + if (password == null || password.isEmpty()) { + synchronized (mOnBackupProcessedListeners) { + for (OnBackupProcessed l : mOnBackupProcessedListeners) { + l.onBackupDecryptionFailed(); + } + } + return false; + } try { SQLiteDatabase db = mDatabaseBackend.getWritableDatabase(); - final FileInputStream fileInputStream = new FileInputStream(file); - final DataInputStream dataInputStream = new DataInputStream(fileInputStream); - BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream); + final InputStream inputStream; + if ("file".equals(uri.getScheme())) { + inputStream = new FileInputStream(new File(uri.getPath())); + } else { + inputStream = getContentResolver().openInputStream(uri); + } + final DataInputStream dataInputStream = new DataInputStream(inputStream); + final BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream); Log.d(Config.LOGTAG, backupFileHeader.toString()); + if (mDatabaseBackend.getAccountJids(false).contains(backupFileHeader.getJid())) { + synchronized (mOnBackupProcessedListeners) { + for (OnBackupProcessed l : mOnBackupProcessedListeners) { + l.onAccountAlreadySetup(); + } + } + return false; + } + final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); byte[] key = ExportBackupService.getKey(password, backupFileHeader.getSalt()); SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); IvParameterSpec ivSpec = new IvParameterSpec(backupFileHeader.getIv()); cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); - CipherInputStream cipherInputStream = new CipherInputStream(fileInputStream, cipher); + CipherInputStream cipherInputStream = new CipherInputStream(inputStream, cipher); GZIPInputStream gzipInputStream = new GZIPInputStream(cipherInputStream); BufferedReader reader = new BufferedReader(new InputStreamReader(gzipInputStream, "UTF-8")); @@ -197,12 +230,7 @@ public class ImportBackupService extends Service { return true; } catch (Exception e) { Throwable throwable = e.getCause(); - final boolean reasonWasCrypto; - if (throwable instanceof BadPaddingException) { - reasonWasCrypto = true; - } else { - reasonWasCrypto = false; - } + final boolean reasonWasCrypto = throwable instanceof BadPaddingException || e instanceof ZipException; synchronized (mOnBackupProcessedListeners) { for (OnBackupProcessed l : mOnBackupProcessedListeners) { if (reasonWasCrypto) { @@ -212,7 +240,7 @@ public class ImportBackupService extends Service { } } } - Log.d(Config.LOGTAG, "error restoring backup " + file.getAbsolutePath(), e); + Log.d(Config.LOGTAG, "error restoring backup " + uri, e); return false; } } @@ -259,14 +287,16 @@ public class ImportBackupService extends Service { void onBackupDecryptionFailed(); void onBackupRestoreFailed(); + + void onAccountAlreadySetup(); } public static class BackupFile { - private final File file; + private final Uri uri; private final BackupFileHeader header; - private BackupFile(File file, BackupFileHeader header) { - this.file = file; + private BackupFile(Uri uri, BackupFileHeader header) { + this.uri = uri; this.header = header; } @@ -275,15 +305,26 @@ public class ImportBackupService extends Service { final DataInputStream dataInputStream = new DataInputStream(fileInputStream); BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream); fileInputStream.close(); - return new BackupFile(file, backupFileHeader); + return new BackupFile(Uri.fromFile(file), backupFileHeader); + } + + public static BackupFile read(final Context context, final Uri uri) throws IOException { + final InputStream inputStream = context.getContentResolver().openInputStream(uri); + if (inputStream == null) { + throw new FileNotFoundException(); + } + final DataInputStream dataInputStream = new DataInputStream(inputStream); + BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream); + inputStream.close(); + return new BackupFile(uri, backupFileHeader); } public BackupFileHeader getHeader() { return header; } - public File getFile() { - return file; + public Uri getUri() { + return uri; } } diff --git a/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java b/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java index d5de333da..7ceff68f7 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java @@ -5,6 +5,8 @@ import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.databinding.DataBindingUtil; +import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.support.design.widget.Snackbar; @@ -13,8 +15,11 @@ import android.support.v7.app.AlertDialog; import android.support.v7.widget.Toolbar; import android.util.Log; import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; +import java.io.IOException; import java.util.List; import eu.siacs.conversations.Config; @@ -23,6 +28,7 @@ import eu.siacs.conversations.databinding.ActivityImportBackupBinding; import eu.siacs.conversations.databinding.DialogEnterPasswordBinding; import eu.siacs.conversations.services.ImportBackupService; import eu.siacs.conversations.ui.adapter.BackupFileAdapter; +import eu.siacs.conversations.ui.util.MenuDoubleTabUtil; import eu.siacs.conversations.utils.ThemeHelper; public class ImportBackupActivity extends ActionBarActivity implements ServiceConnection, ImportBackupService.OnBackupFilesLoaded, BackupFileAdapter.OnItemClickedListener, ImportBackupService.OnBackupProcessed { @@ -32,6 +38,8 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo private BackupFileAdapter backupFileAdapter; private ImportBackupService service; + private boolean mLoadingState = false; + private int mTheme; @Override @@ -47,6 +55,14 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo this.backupFileAdapter.setOnItemClickedListener(this); } + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + getMenuInflater().inflate(R.menu.import_backup, menu); + final MenuItem openBackup = menu.findItem(R.id.action_open_backup_file); + openBackup.setVisible(!this.mLoadingState); + return true; + } + @Override public void onStart() { super.onStart(); @@ -87,9 +103,22 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo } @Override - public void onClick(ImportBackupService.BackupFile backupFile) { + public void onClick(final ImportBackupService.BackupFile backupFile) { + showEnterPasswordDialog(backupFile); + } + + private void openBackupFileFromUri(final Uri uri) { + try { + final ImportBackupService.BackupFile backupFile = ImportBackupService.BackupFile.read(this, uri); + showEnterPasswordDialog(backupFile); + } catch (IOException e) { + Snackbar.make(binding.coordinator, R.string.not_a_backup_file, Snackbar.LENGTH_LONG).show(); + } + } + + private void showEnterPasswordDialog(final ImportBackupService.BackupFile backupFile) { final DialogEnterPasswordBinding enterPasswordBinding = DataBindingUtil.inflate(LayoutInflater.from(this), R.layout.dialog_enter_password, null, false); - Log.d(Config.LOGTAG, "attempting to import " + backupFile.getFile().getAbsolutePath()); + Log.d(Config.LOGTAG, "attempting to import " + backupFile.getUri()); enterPasswordBinding.explain.setText(getString(R.string.enter_password_to_restore, backupFile.getHeader().getJid().toString())); AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setView(enterPasswordBinding.getRoot()); @@ -97,9 +126,16 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo builder.setNegativeButton(R.string.cancel, null); builder.setPositiveButton(R.string.restore, (dialog, which) -> { final String password = enterPasswordBinding.accountPassword.getEditableText().toString(); + final Uri uri = backupFile.getUri(); Intent intent = new Intent(this, ImportBackupService.class); + intent.setAction(Intent.ACTION_SEND); intent.putExtra("password", password); - intent.putExtra("file", backupFile.getFile().getAbsolutePath()); + if ("file".equals(uri.getScheme())) { + intent.putExtra("file", uri.getPath()); + } else { + intent.setData(uri); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } setLoadingState(true); ContextCompat.startForegroundService(this, intent); }); @@ -108,10 +144,29 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo } private void setLoadingState(final boolean loadingState) { - binding.coordinator.setVisibility(loadingState ? View.GONE :View.VISIBLE); + binding.coordinator.setVisibility(loadingState ? View.GONE : View.VISIBLE); binding.inProgress.setVisibility(loadingState ? View.VISIBLE : View.GONE); setTitle(loadingState ? R.string.restoring_backup : R.string.restore_backup); - configureActionBar(getSupportActionBar(),!loadingState); + configureActionBar(getSupportActionBar(), !loadingState); + this.mLoadingState = loadingState; + invalidateOptionsMenu(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + if (resultCode == RESULT_OK) { + if (requestCode == 0xbac) { + openBackupFileFromUri(intent.getData()); + } + } + } + + @Override + public void onAccountAlreadySetup() { + runOnUiThread(() -> { + setLoadingState(false); + Snackbar.make(binding.coordinator, R.string.account_already_setup, Snackbar.LENGTH_LONG).show(); + }); } @Override @@ -126,17 +181,33 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo @Override public void onBackupDecryptionFailed() { - runOnUiThread(()-> { + runOnUiThread(() -> { setLoadingState(false); - Snackbar.make(binding.coordinator,R.string.unable_to_decrypt_backup,Snackbar.LENGTH_LONG).show(); + Snackbar.make(binding.coordinator, R.string.unable_to_decrypt_backup, Snackbar.LENGTH_LONG).show(); }); } @Override public void onBackupRestoreFailed() { - runOnUiThread(()-> { + runOnUiThread(() -> { setLoadingState(false); - Snackbar.make(binding.coordinator,R.string.unable_to_restore_backup,Snackbar.LENGTH_LONG).show(); + Snackbar.make(binding.coordinator, R.string.unable_to_restore_backup, Snackbar.LENGTH_LONG).show(); }); } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_open_backup_file: + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("*/*"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); + } + intent.addCategory(Intent.CATEGORY_OPENABLE); + startActivityForResult(Intent.createChooser(intent, getString(R.string.open_backup)), 0xbac); + return true; + } + return super.onOptionsItemSelected(item); + } } diff --git a/src/main/res/drawable-hdpi/ic_cloud_download_white_24dp.png b/src/main/res/drawable-hdpi/ic_cloud_download_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..4c5d2d049e392a237958679a6599519c7f59cfd5 GIT binary patch literal 353 zcmV-n0iOPeP)Ds!(`sk%0Imd!2!g#;^aK|14&oK&0Agip;Ro0XVm!fOA$tM8h?s!JLJdM9 zN*;?T%p^0rZ(OjDJpcaJ4D-sk{}n|xIpu*4pENjUiy{)5;NKM6bQrA54%4GRi`)?E zjw{K!!=Loc?S2@V zrwt=R(By=5CV!w9RFJ<*<}6f!To61GA;Mm62oeh7eegQQ&Vh^_b%xsz-tpxUF6Zk^ z_y9c>VBlG}1vO;|^(7pFk}`yA32UID3~AK_;X*+f(yIzWiFP`b$s`oQpH|-!5hB=? zi1}R!(cpz#WuI%h=<3m;MwJ3$q5K;|e+_*DoPSf;fViWX00000NkvXXu0mjf?unJ- literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-mdpi/ic_cloud_download_white_24dp.png b/src/main/res/drawable-mdpi/ic_cloud_download_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..0c6978c1fd094c1d045389e83aed90b724495b1c GIT binary patch literal 242 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_+iot`d^Ar^vn58C=M1xmDCoX!== z+&Q&VIzc>z%c^|g(Grt*Rn=o{X9IltFJ>${V|Qjop@y7F+@ZfSv)5R%j6|idht{$r%KdvdG=I3 qtrz=sbV9UQ&BW^gfiHaCFdn(ZwdkJK-s?b5FnGH9xvX)D*jn?auJVYUT*KKR0m$E(zB|-j;dX+0$g&eYKP9}0PF7g=t!OJ z&jLCMBlSD9It|z{Fs+!J)4Ey?+=01)y)s~%Mu4F&Ufa}~1Ud$_fbzcD_!lT>7=TC4 zjDe2PLI56*fn62^uqnd+0)d{@@Kbq7WGpGK8&1e4DxRm91(4=A&$sY{+C(y|Vc4f8 zk;nmdsYN6?9fT`J)d@*nMF?-HNF=#+Ls&DYL?pTOv_lBD$R?8fwnP~3w&r^>`DEVo zTa)cMI|+o#-WX}3o~^QP%bL4B_-?39Lk9|7ZBYg^wEqfN0!!d8m2gFXs7S&200000 LNkvXXu0mjfq%yl{ literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xxhdpi/ic_cloud_download_white_24dp.png b/src/main/res/drawable-xxhdpi/ic_cloud_download_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..3392d972fe024190e6d77048018cabf9185f42d8 GIT binary patch literal 610 zcmV-o0-gPdP)MSTYjf}^-3lMOyWom`6O;OJ20xG5q;ks?S25v$}QfCy47Za3Yla;8Af=GPOmsZ#y4mu&xlil9sWQOr|A&4 z4kzjJ0UF>Arrl3^R?dF2@Cxn8RmZSg#Knphlde%l_U$UC?G`AOX<09%zsz zBm#P?1M24qBm{yc6P#feeceGda0t@vB`3)L2~>hqKmczj{SL}9mm&z@EYZ)P!;mrv zt`V(*9`yhSF8ly(g`NPx&^Lp5!uRdLCgq3&qlmDwEA}L7&OC)xP@cz@B!z92UmuD^ zW=XS)YoF3Zrb(+?L~B7pp0Sj^PSpJzvzu9xY@W)$4ptEdm?DW1|&)--fBOT!<-Ol0Y&M(CX0zFBHk(aV zRC9rT#)z{>iX3Y!v&cIh)6aSKBh-${$BuG~x8&Ha9J5>_;y*we&snF`ay+BeKY(+@ zanc*kmdI5X_fU~;=w3GK0;5#!s;0nh|12mHYF8~foaDxNe11k6gz6AiJknGzU&<{Qb z04&h9IUr0r5CF(CKfP-WL1%Pov#enM&FaX>w2E+phfQ$bCO%O=H z5;X-tS3m(UQ~<;R3&_%l5WK=!5}|M;P8vW6=D|2Y;;e6k1j}HUAaUMF!5SDPNZb@H z0L&32?wS<*1Y-n=+Y*9#Fhr2J?}y+e7$Hbhv?>?|0|W`G0A1iYL87Xp;4pYhkfuBGtseei%FQQZQe!qZKpwG$-j8$<{S69ugV ziTbjHi+?SC)<}@(D7NwE?g>(w2$G`If7BQ6X4W+jBzo%DY{Ah(P>OA}V(MWX|2IY1 zRscaezwvid?A?CNdYn1@Ta2=u7u^-yCXN4EqNDUvgD^v6@K0HWsoCwZM;(2{$z!?& z1_?Vqw`ri0d%WN)NmlWyRgxr_AW91%^@p#9#{w+C0xZA+EWiRRzyd750u;<&TKF}4 Tehrn800000NkvXXu0mjfJxo+? literal 0 HcmV?d00001 diff --git a/src/main/res/menu/import_backup.xml b/src/main/res/menu/import_backup.xml new file mode 100644 index 000000000..0d8a14497 --- /dev/null +++ b/src/main/res/menu/import_backup.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/src/main/res/values/attrs.xml b/src/main/res/values/attrs.xml index e2f7ddf76..1f78b19a2 100644 --- a/src/main/res/values/attrs.xml +++ b/src/main/res/values/attrs.xml @@ -43,6 +43,9 @@ + + + diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 90866434e..08f9b02e0 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -871,4 +871,7 @@ Share backup files Conversations backup Event + Open backup + The file you selected is not a Conversations backup file + This account has already been setup diff --git a/src/main/res/values/themes.xml b/src/main/res/values/themes.xml index f4c870603..8866e9931 100644 --- a/src/main/res/values/themes.xml +++ b/src/main/res/values/themes.xml @@ -98,6 +98,7 @@ @drawable/ic_lock_open_white_24dp @drawable/ic_settings_black_24dp @drawable/ic_share_white_24dp + @drawable/ic_cloud_download_white_24dp @drawable/ic_qr_code_scan_white_24dp @drawable/ic_scroll_to_end_black @@ -212,6 +213,7 @@ @drawable/ic_lock_open_white_24dp @drawable/ic_settings_white_24dp @drawable/ic_share_white_24dp + @drawable/ic_cloud_download_white_24dp @drawable/ic_qr_code_scan_white_24dp @drawable/ic_scroll_to_end_white From cb1feab350fb5cf90ecefe75b354472e492fa1c9 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 17 Jul 2019 11:32:56 +0200 Subject: [PATCH 11/19] open backup files on view action --- src/conversations/AndroidManifest.xml | 15 +++++- .../ui/ImportBackupActivity.java | 51 ++++++++++++------- src/main/AndroidManifest.xml | 1 - 3 files changed, 47 insertions(+), 20 deletions(-) diff --git a/src/conversations/AndroidManifest.xml b/src/conversations/AndroidManifest.xml index 0a01c6c0f..a91f4c129 100644 --- a/src/conversations/AndroidManifest.xml +++ b/src/conversations/AndroidManifest.xml @@ -23,7 +23,20 @@ + android:launchMode="singleTask"> + + + + + + + + + + + + + diff --git a/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java b/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java index 7ceff68f7..bca907b33 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java @@ -28,7 +28,6 @@ import eu.siacs.conversations.databinding.ActivityImportBackupBinding; import eu.siacs.conversations.databinding.DialogEnterPasswordBinding; import eu.siacs.conversations.services.ImportBackupService; import eu.siacs.conversations.ui.adapter.BackupFileAdapter; -import eu.siacs.conversations.ui.util.MenuDoubleTabUtil; import eu.siacs.conversations.utils.ThemeHelper; public class ImportBackupActivity extends ActionBarActivity implements ServiceConnection, ImportBackupService.OnBackupFilesLoaded, BackupFileAdapter.OnItemClickedListener, ImportBackupService.OnBackupProcessed { @@ -49,7 +48,7 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo super.onCreate(savedInstanceState); binding = DataBindingUtil.setContentView(this, R.layout.activity_import_backup); setSupportActionBar((Toolbar) binding.toolbar); - configureActionBar(getSupportActionBar()); + setLoadingState(savedInstanceState != null && savedInstanceState.getBoolean("loading_state", false)); this.backupFileAdapter = new BackupFileAdapter(); this.binding.list.setAdapter(this.backupFileAdapter); this.backupFileAdapter.setOnItemClickedListener(this); @@ -63,6 +62,12 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo return true; } + @Override + public void onSaveInstanceState(Bundle bundle) { + bundle.putBoolean("loading_state", this.mLoadingState); + super.onSaveInstanceState(bundle); + } + @Override public void onStart() { super.onStart(); @@ -72,6 +77,13 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo } else { bindService(new Intent(this, ImportBackupService.class), this, Context.BIND_AUTO_CREATE); } + final Intent intent = getIntent(); + if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction()) && !this.mLoadingState) { + Uri uri = intent.getData(); + if (uri != null) { + openBackupFileFromUri(uri, true); + } + } } @Override @@ -104,26 +116,30 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo @Override public void onClick(final ImportBackupService.BackupFile backupFile) { - showEnterPasswordDialog(backupFile); + showEnterPasswordDialog(backupFile, false); } - private void openBackupFileFromUri(final Uri uri) { + private void openBackupFileFromUri(final Uri uri, final boolean finishOnCancel) { try { final ImportBackupService.BackupFile backupFile = ImportBackupService.BackupFile.read(this, uri); - showEnterPasswordDialog(backupFile); + showEnterPasswordDialog(backupFile, finishOnCancel); } catch (IOException e) { Snackbar.make(binding.coordinator, R.string.not_a_backup_file, Snackbar.LENGTH_LONG).show(); } } - private void showEnterPasswordDialog(final ImportBackupService.BackupFile backupFile) { + private void showEnterPasswordDialog(final ImportBackupService.BackupFile backupFile, final boolean finishOnCancel) { final DialogEnterPasswordBinding enterPasswordBinding = DataBindingUtil.inflate(LayoutInflater.from(this), R.layout.dialog_enter_password, null, false); Log.d(Config.LOGTAG, "attempting to import " + backupFile.getUri()); enterPasswordBinding.explain.setText(getString(R.string.enter_password_to_restore, backupFile.getHeader().getJid().toString())); AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setView(enterPasswordBinding.getRoot()); builder.setTitle(R.string.enter_password); - builder.setNegativeButton(R.string.cancel, null); + builder.setNegativeButton(R.string.cancel, (dialog, which) -> { + if (finishOnCancel) { + finish(); + } + }); builder.setPositiveButton(R.string.restore, (dialog, which) -> { final String password = enterPasswordBinding.accountPassword.getEditableText().toString(); final Uri uri = backupFile.getUri(); @@ -156,7 +172,7 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo public void onActivityResult(int requestCode, int resultCode, Intent intent) { if (resultCode == RESULT_OK) { if (requestCode == 0xbac) { - openBackupFileFromUri(intent.getData()); + openBackupFileFromUri(intent.getData(), false); } } } @@ -197,16 +213,15 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo @Override public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_open_backup_file: - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("*/*"); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); - } - intent.addCategory(Intent.CATEGORY_OPENABLE); - startActivityForResult(Intent.createChooser(intent, getString(R.string.open_backup)), 0xbac); - return true; + if (item.getItemId() == R.id.action_open_backup_file) { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("*/*"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); + } + intent.addCategory(Intent.CATEGORY_OPENABLE); + startActivityForResult(Intent.createChooser(intent, getString(R.string.open_backup)), 0xbac); + return true; } return super.onOptionsItemSelected(item); } diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index 6b43106f9..31f33e5fd 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -92,7 +92,6 @@ android:theme="@style/SplashTheme"> - From d9f39df9c8e6e968366ab948d4472789b0823966 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 17 Jul 2019 17:21:18 +0200 Subject: [PATCH 12/19] do password empty check in dialog not in restore backup service --- .../services/ImportBackupService.java | 10 +---- .../ui/ImportBackupActivity.java | 42 ++++++++++++------- src/main/res/values/strings.xml | 1 + 3 files changed, 28 insertions(+), 25 deletions(-) diff --git a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java index b982e1be8..d8246a6af 100644 --- a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java +++ b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java @@ -93,7 +93,7 @@ public class ImportBackupService extends Service { uri = data; } - if (password == null || uri == null) { + if (password == null || password.isEmpty() || uri == null) { return START_NOT_STICKY; } if (running.compareAndSet(false, true)) { @@ -158,14 +158,6 @@ public class ImportBackupService extends Service { private boolean importBackup(Uri uri, String password) { Log.d(Config.LOGTAG, "importing backup from " + uri); - if (password == null || password.isEmpty()) { - synchronized (mOnBackupProcessedListeners) { - for (OnBackupProcessed l : mOnBackupProcessedListeners) { - l.onBackupDecryptionFailed(); - } - } - return false; - } try { SQLiteDatabase db = mDatabaseBackend.getWritableDatabase(); final InputStream inputStream; diff --git a/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java b/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java index bca907b33..d6dbfd222 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java @@ -2,6 +2,7 @@ package eu.siacs.conversations.ui; import android.content.ComponentName; import android.content.Context; +import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; import android.databinding.DataBindingUtil; @@ -140,23 +141,32 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo finish(); } }); - builder.setPositiveButton(R.string.restore, (dialog, which) -> { - final String password = enterPasswordBinding.accountPassword.getEditableText().toString(); - final Uri uri = backupFile.getUri(); - Intent intent = new Intent(this, ImportBackupService.class); - intent.setAction(Intent.ACTION_SEND); - intent.putExtra("password", password); - if ("file".equals(uri.getScheme())) { - intent.putExtra("file", uri.getPath()); - } else { - intent.setData(uri); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - } - setLoadingState(true); - ContextCompat.startForegroundService(this, intent); - }); + builder.setPositiveButton(R.string.restore, null); builder.setCancelable(false); - builder.create().show(); + final AlertDialog dialog = builder.create(); + dialog.setOnShowListener((d) -> { + dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> { + final String password = enterPasswordBinding.accountPassword.getEditableText().toString(); + if (password.isEmpty()) { + enterPasswordBinding.accountPasswordLayout.setError(getString(R.string.please_enter_password)); + return; + } + final Uri uri = backupFile.getUri(); + Intent intent = new Intent(this, ImportBackupService.class); + intent.setAction(Intent.ACTION_SEND); + intent.putExtra("password", password); + if ("file".equals(uri.getScheme())) { + intent.putExtra("file", uri.getPath()); + } else { + intent.setData(uri); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + } + setLoadingState(true); + ContextCompat.startForegroundService(this, intent); + d.dismiss(); + }); + }); + dialog.show(); } private void setLoadingState(final boolean loadingState) { diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 08f9b02e0..8a8d31757 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -874,4 +874,5 @@ Open backup The file you selected is not a Conversations backup file This account has already been setup + Please enter the password for this account From 6de6d054acc2c8cf371562339c354767fb1efb28 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 17 Jul 2019 21:03:56 +0200 Subject: [PATCH 13/19] catch fcm library bugs --- .../conversations/services/PushManagementService.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java b/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java index 2a3d1a648..1fc8e58fe 100644 --- a/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java +++ b/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java @@ -151,7 +151,13 @@ public class PushManagementService { if (!task.isSuccessful()) { Log.d(Config.LOGTAG, "unable to get Firebase instance token", task.getException()); } - final InstanceIdResult result = task.getResult(); + final InstanceIdResult result; + try { + result = task.getResult(); + } catch (Exception e) { + Log.d(Config.LOGTAG, "unable to get Firebase instance token due to bug in library ", e); + return; + } if (result != null) { instanceTokenRetrieved.onGcmInstanceTokenRetrieved(result.getToken()); } From 3837ec8122a24269409d9d8e5f3949928f3277e8 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 18 Jul 2019 11:23:07 +0200 Subject: [PATCH 14/19] do not include DNS servers from networks know to be inactive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * we still include DNS servers from VPNs because of edge cases where the XMPP server is hosted in the VPN * on older Android versions we don’t know if a network is active or not (activeNetwork == null) fixes #3465 --- .../utils/AndroidUsingLinkProperties.java | 27 +++++++++---------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/utils/AndroidUsingLinkProperties.java b/src/main/java/eu/siacs/conversations/utils/AndroidUsingLinkProperties.java index d6a9f9a1a..658e7abcd 100644 --- a/src/main/java/eu/siacs/conversations/utils/AndroidUsingLinkProperties.java +++ b/src/main/java/eu/siacs/conversations/utils/AndroidUsingLinkProperties.java @@ -21,7 +21,7 @@ public class AndroidUsingLinkProperties extends AbstractDNSServerLookupMechanism private final Context context; - protected AndroidUsingLinkProperties(Context context) { + AndroidUsingLinkProperties(Context context) { super(AndroidUsingLinkProperties.class.getSimpleName(), AndroidUsingExec.PRIORITY - 1); this.context = context; } @@ -34,32 +34,31 @@ public class AndroidUsingLinkProperties extends AbstractDNSServerLookupMechanism @Override @TargetApi(21) public String[] getDnsServerAddresses() { - ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); - Network[] networks = connectivityManager == null ? null : connectivityManager.getAllNetworks(); + final ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); + final Network[] networks = connectivityManager == null ? null : connectivityManager.getAllNetworks(); if (networks == null) { return new String[0]; } final Network activeNetwork = getActiveNetwork(connectivityManager); - List servers = new ArrayList<>(); + final List servers = new ArrayList<>(); int vpnOffset = 0; for(Network network : networks) { LinkProperties linkProperties = connectivityManager.getLinkProperties(network); if (linkProperties == null) { continue; } - NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network); + final NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network); final boolean isActiveNetwork = network.equals(activeNetwork); - if (networkInfo != null && isActiveNetwork && networkInfo.getType() == ConnectivityManager.TYPE_VPN) { + final boolean isVpn = networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_VPN; + if (isActiveNetwork && isVpn) { final List tmp = getIPv4First(linkProperties.getDnsServers()); servers.addAll(0, tmp); vpnOffset += tmp.size(); - } else if (hasDefaultRoute(linkProperties) || isActiveNetwork) { + } else if (hasDefaultRoute(linkProperties) || isActiveNetwork || activeNetwork == null || isVpn) { servers.addAll(vpnOffset, getIPv4First(linkProperties.getDnsServers())); - } else { - servers.addAll(getIPv4First(linkProperties.getDnsServers())); } } - return servers.toArray(new String[servers.size()]); + return servers.toArray(new String[0]); } @TargetApi(23) @@ -69,11 +68,11 @@ public class AndroidUsingLinkProperties extends AbstractDNSServerLookupMechanism private static List getIPv4First(List in) { List out = new ArrayList<>(); - for(InetAddress addr : in) { - if (addr instanceof Inet4Address) { - out.add(0, addr.getHostAddress()); + for(InetAddress address : in) { + if (address instanceof Inet4Address) { + out.add(0, address.getHostAddress()); } else { - out.add(addr.getHostAddress()); + out.add(address.getHostAddress()); } } return out; From f5be53d08a913954f9cb8de1825c0770aeb17b6e Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 20 Jul 2019 12:14:33 +0200 Subject: [PATCH 15/19] jump into last field after re-enable pin entry --- .../siacs/conversations/ui/util/PinEntryWrapper.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/quicksy/java/eu/siacs/conversations/ui/util/PinEntryWrapper.java b/src/quicksy/java/eu/siacs/conversations/ui/util/PinEntryWrapper.java index 17da72798..8e91f659d 100644 --- a/src/quicksy/java/eu/siacs/conversations/ui/util/PinEntryWrapper.java +++ b/src/quicksy/java/eu/siacs/conversations/ui/util/PinEntryWrapper.java @@ -119,13 +119,19 @@ public class PinEntryWrapper { } } - public void setEnabled(boolean enabled) { - for(EditText digit : digits) { + public void setEnabled(final boolean enabled) { + for (EditText digit : digits) { digit.setEnabled(enabled); digit.setCursorVisible(enabled); digit.setFocusable(enabled); digit.setFocusableInTouchMode(enabled); } + if (enabled) { + final EditText last = digits.get(digits.size() - 1); + if (last.getEditableText().length() > 0) { + last.requestFocus(); + } + } } public boolean isEmpty() { From c1589d76b59aa667e5f95c955a252f0ae9ee961a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 20 Jul 2019 12:15:06 +0200 Subject: [PATCH 16/19] resetToWaiting should include http uploaded files --- src/main/java/eu/siacs/conversations/entities/Conversation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index c9ec5637a..2004953ee 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -285,7 +285,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl final ArrayList results = new ArrayList<>(); synchronized (this.messages) { for (Message message : this.messages) { - if (message.getType() != Message.TYPE_IMAGE && message.getStatus() == Message.STATUS_UNSEND) { + if ((message.getType() == Message.TYPE_TEXT || message.hasFileOnRemoteHost()) && message.getStatus() == Message.STATUS_UNSEND) { results.add(message); } } From c87e43eb92e5abd37fbf66fec4474776dab87a34 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 20 Jul 2019 15:58:05 +0200 Subject: [PATCH 17/19] put initial xmpp uri into signup intent --- .../java/eu/siacs/conversations/utils/SignupUtils.java | 3 +-- .../java/eu/siacs/conversations/ui/UriHandlerActivity.java | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java b/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java index 6e8ed2eff..fc5d874d3 100644 --- a/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java +++ b/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java @@ -20,13 +20,12 @@ public class SignupUtils { } public static Intent getSignUpIntent(final Activity activity, final boolean toServerChooser) { - Intent intent; + final Intent intent; if (toServerChooser) { intent = new Intent(activity, PickServerActivity.class); } else { intent = new Intent(activity, WelcomeActivity.class); } - StartConversationActivity.addInviteUri(intent, activity.getIntent()); return intent; } diff --git a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java index 95f179ceb..d1c0fa7d4 100644 --- a/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/UriHandlerActivity.java @@ -90,6 +90,7 @@ public class UriHandlerActivity extends AppCompatActivity { if (accounts.size() == 0) { if (xmppUri.isJidValid()) { intent = SignupUtils.getSignUpIntent(this); + intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString()); startActivity(intent); } else { Toast.makeText(this, R.string.invalid_jid, Toast.LENGTH_SHORT).show(); From a4665d4657f0b02c60c807e371911451c4e5bb14 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 20 Jul 2019 17:51:37 +0200 Subject: [PATCH 18/19] made domain verifier case insensitive. fixes #3495 --- .../crypto/XmppDomainVerifier.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/crypto/XmppDomainVerifier.java b/src/main/java/eu/siacs/conversations/crypto/XmppDomainVerifier.java index f4ca658d4..1b618c5ad 100644 --- a/src/main/java/eu/siacs/conversations/crypto/XmppDomainVerifier.java +++ b/src/main/java/eu/siacs/conversations/crypto/XmppDomainVerifier.java @@ -22,6 +22,7 @@ import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Locale; import javax.net.ssl.SSLSession; @@ -80,14 +81,14 @@ public class XmppDomainVerifier implements DomainHostnameVerifier { break; } Log.d(LOGTAG, "comparing " + needle.substring(i) + " and " + entry.substring(1)); - if (needle.substring(i).equals(entry.substring(1))) { + if (needle.substring(i).equalsIgnoreCase(entry.substring(1))) { Log.d(LOGTAG, "domain " + needle + " matched " + entry); return true; } offset = i + 1; } } else { - if (entry.equals(needle)) { + if (entry.equalsIgnoreCase(needle)) { Log.d(LOGTAG, "domain " + needle + " matched " + entry); return true; } @@ -117,25 +118,25 @@ public class XmppDomainVerifier implements DomainHostnameVerifier { List domains = new ArrayList<>(); if (alternativeNames != null) { for (List san : alternativeNames) { - Integer type = (Integer) san.get(0); + final Integer type = (Integer) san.get(0); if (type == 0) { - Pair otherName = parseOtherName((byte[]) san.get(1)); - if (otherName != null) { + final Pair otherName = parseOtherName((byte[]) san.get(1)); + if (otherName != null && otherName.first != null && otherName.second != null) { switch (otherName.first) { case SRV_NAME: - srvNames.add(otherName.second); + srvNames.add(otherName.second.toLowerCase(Locale.US)); break; case XMPP_ADDR: - xmppAddrs.add(otherName.second); + xmppAddrs.add(otherName.second.toLowerCase(Locale.US)); break; default: Log.d(LOGTAG, "oid: " + otherName.first + " value: " + otherName.second); } } } else if (type == 2) { - Object value = san.get(1); + final Object value = san.get(1); if (value instanceof String) { - domains.add((String) value); + domains.add(((String) value).toLowerCase(Locale.US)); } } } From de66e9b6f99890c69848c1635fe06c3164679273 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 23 Jul 2019 08:35:29 +0200 Subject: [PATCH 19/19] version bump to 2.5.5 + changelog --- CHANGELOG.md | 4 ++++ build.gradle | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 42fe078ee..c57da8926 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +### Version 2.5.5 +* allow backups to be restored from anywhere +* bug fixes + ### Version 2.5.4 * stability improvements for group chats and channels diff --git a/build.gradle b/build.gradle index 5aad569c6..23058b43c 100644 --- a/build.gradle +++ b/build.gradle @@ -81,8 +81,8 @@ android { defaultConfig { minSdkVersion 16 targetSdkVersion 28 - versionCode 333 - versionName "2.5.4" + versionCode 334 + versionName "2.5.5" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId