diff --git a/CHANGELOG.md b/CHANGELOG.md index 532d3c0b9..e58b08305 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +### Version 2.9.2 + +* Offer Easy Invite generation on supporting servers +* Display GIFs send from Movim +* store avatars in cache + +### Version 2.9.1 + +* fixed search on Android <= 5 +* optimize memory consumption + ### Version 2.9.0 * Search individual conversations @@ -419,10 +430,9 @@ * Icons for attach menu ### Version 1.16.2 -* change mam catchup strategie. support mam:1 +* change mam catchup strategy. support mam:1 * bug fixes - ### Version 1.16.1 * UI performance fixes * bug fixes @@ -472,7 +482,7 @@ * bug fixes ### Version 1.14.6 -* make error notification dismissable +* make error notification dismissible * bug fixes @@ -496,7 +506,7 @@ * bug fixes ### Version 1.14.0 -* Improvments for N +* Improvements for N * Quick Reply to Notifications on N * Don't download avatars and files when data saver is on * bug fixes @@ -674,7 +684,7 @@ ### Version 1.7.0 * CAPTCHA support -* SASL EXTERNAL (client certifiates) +* SASL EXTERNAL (client certificates) * fetching MUC history via MAM * redownload deleted files from HTTP hosts * Expert setting to automatically set presence @@ -782,7 +792,7 @@ * accept more ciphers ### Version 1.0 -* MUC controls (Affiliaton changes) +* MUC controls (Affiliation changes) * Added download button to notification * Added check box to hide offline contacts * Use Material theme and icons on Android L @@ -888,7 +898,7 @@ * XEP-0333. Mark whether the other party has read your messages * Delayed messages are now tagged properly * Share images from the Gallery -* Infinit history scrolling +* Infinite history scrolling * Mark the last used presence in presence selection dialog ### Version 0.3 diff --git a/README.md b/README.md index da7166cb5..34185b185 100644 --- a/README.md +++ b/README.md @@ -141,7 +141,7 @@ Maybe you attempted to use the Jabber ID `test@b.tld` because `a.tld` doesn’t ### I get 'Stream opening error'. What does that mean? -In most cases this error is caused by ejabberd advertising support for TLSv1.3 but not properly supporting it. This can happen if the openssl version on the server already supports TLSv1.3 but the fast\_tls wrapper library used by ejabberd not (properly) support it. Upgrading fast\_tls and ejabberd or - theoretically - downgrading openssl should fix the issue. A work around is to explicity disable TLSv1.3 support in the ejabberd configuration. More information can be found on [this issue on the ejabberd issue tracker](https://github.com/processone/ejabberd/issues/2614). +In most cases this error is caused by ejabberd advertising support for TLSv1.3 but not properly supporting it. This can happen if the OpenSSL version on the server already supports TLSv1.3 but the fast\_tls wrapper library used by ejabberd not (properly) support it. Upgrading fast\_tls and ejabberd or - theoretically - downgrading OpenSSL should fix the issue. A work around is to explicitly disable TLSv1.3 support in the ejabberd configuration. More information can be found on [this issue on the ejabberd issue tracker](https://github.com/processone/ejabberd/issues/2614). #### I’m getting this annoying permanent notification @@ -149,7 +149,7 @@ Starting with Conversations 2.3.6 Conversations releases distributed over the Go However you can disable the notification via settings of the operating system. (Not settings in Conversations.) -**The battery consumption and the entire behaviour of Conversations will remain the same (as good or as bad as it was before). Why is Google doing this to you? We have no idea.** +**The battery consumption and the entire behavior of Conversations will remain the same (as good or as bad as it was before). Why is Google doing this to you? We have no idea.** ##### Android <= 7.1 or Conversations from F-Droid (all Android versions) The foreground notification is still controlled over the expert settings within Conversations as it always has been. Whether or not you need to enable it depends on how aggressive the non-standard 'power saving' features are that your phone vendor has built into the operating system. @@ -173,7 +173,7 @@ You can find a detailed description of how your server, the app server and FCM a #### But why do I need a permanent notification if I use Google Push? -FCM (Google Push) allows an app to wake up from *Doze* which is (as the name suggests) a hibernation feature of the Android operating system that cuts the network connection and also reduces the number of times the app is allowed to wake up (to ping the server for example). The app can ask to be excluded from doze. Non push variants of the app (from F-Droid or if the server doesn’t support it) will do this on first start up. So if you get exemption from *Doze*, or if you get regular push events sent to you, Doze should not pose a threat to Conversatons working properly. But even with *Doze* the app is still open in the background (kept in memory); it is just limited in the actions it can do. Conversations needs to stay in memory to hold certain session state (online status of contacts, join status of group chats, …). However with Android 8 Google changed all of this again and now an App that wants to stay in memory needs to have a foreground service which is visible to the user via the annoying notification. But why does Conversations need to hold that state? XMPP is a stateful protocol that has a lot of per-session information; packets need to be counted, presence information needs to be held, some features like Message Carbons get activated once per session, MAM catchup happens once, service discovery happens only once; the list goes on. When Conversations was created in early 2014 none of this was a problem because apps were just allowed to stay in memory. Basically every XMPP client out there holds that information in memory because it would be a lot more complicated trying to persist it to disk. An entire rewrite of Conversations in the year 2019 would attempt to do that and would probably succeed however it would require exactly that; a complete rewrite which is not feasible right now. That’s by the way also the reason why it is difficult to write an XMPP client on iOS. Or more broadly put this is also the reason why other protocols are designed as or migrated to stateless protocols (often based on HTTP); take for example the migration of IMAP to [JMAP](https://jmap.io/). +FCM (Google Push) allows an app to wake up from *Doze* which is (as the name suggests) a hibernation feature of the Android operating system that cuts the network connection and also reduces the number of times the app is allowed to wake up (to ping the server for example). The app can ask to be excluded from doze. Non push variants of the app (from F-Droid or if the server doesn’t support it) will do this on first start up. So if you get exemption from *Doze*, or if you get regular push events sent to you, Doze should not pose a threat to Conversatons working properly. But even with *Doze* the app is still open in the background (kept in memory); it is just limited in the actions it can do. Conversations needs to stay in memory to hold certain session state (online status of contacts, join status of group chats, …). However with Android 8 Google changed all of this again and now an App that wants to stay in memory needs to have a foreground service which is visible to the user via the annoying notification. But why does Conversations need to hold that state? XMPP is a statefull protocol that has a lot of per-session information; packets need to be counted, presence information needs to be held, some features like Message Carbons get activated once per session, MAM catch-up happens once, service discovery happens only once; the list goes on. When Conversations was created in early 2014 none of this was a problem because apps were just allowed to stay in memory. Basically every XMPP client out there holds that information in memory because it would be a lot more complicated trying to persist it to disk. An entire rewrite of Conversations in the year 2019 would attempt to do that and would probably succeed however it would require exactly that; a complete rewrite which is not feasible right now. That’s by the way also the reason why it is difficult to write an XMPP client on iOS. Or more broadly put this is also the reason why other protocols are designed as or migrated to stateless protocols (often based on HTTP); take for example the migration of IMAP to [JMAP](https://jmap.io/). #### Conversations doesn’t work for me. Where can I get help? @@ -267,11 +267,11 @@ and introduce yourself to `iNPUTmice` so he can approve your join request. #### How do I backup / move Conversations to a new device? On the one hand Conversations supports Message Archive Management to keep a server side history of your messages so when migrating to a new device that device can display your entire history. However that does not work if you enable OMEMO due to its forward secrecy. (Read [The State of Mobile XMPP in 2016](https://gultsch.de/xmpp_2016.html) especially the section on encryption.) -As of version 2.4.0 an integrated Backup & Restore function will help with this, go to Settings and you’ll find a setting called Create backup. A notification will pop-up during the creation process that will announce you when it's ready. After the files, one for each account, are created, you can move the **Conversations** folder *(if you want your old media files too)* or only the **Conversations/Backup** folder *(for OMEMO keys and history only)* to your new device (or to a storage place) where a freshly installed Conversations can restore each account. Don't forget to enable the accounts after a succesful restore. +As of version 2.4.0 an integrated Backup & Restore function will help with this, go to Settings and you’ll find a setting called Create backup. A notification will pop-up during the creation process that will announce you when it's ready. After the files, one for each account, are created, you can move the **Conversations** folder *(if you want your old media files too)* or only the **Conversations/Backup** folder *(for OMEMO keys and history only)* to your new device (or to a storage place) where a freshly installed Conversations can restore each account. Don't forget to enable the accounts after a successfull restore. This backup method will include your OMEMO keys. Due to forward secrecy you will not be able to recover messages sent and received between creating the backup and restoring it. If you have a server side archive (MAM) those messages will be retrieved but displayed as *unable to decrypt*. For technical reasons you might also lose the first message you either sent or receive after the restore; for each conversation you have. This message will then also show up as *unable to decrypt*, but this will automatically recover itself as long as both participants are on Conversations 2.3.11+. Note that this doesn’t happen if you just transfer to a new phone and no messages have been exchanged between backup and restore. -In the vast, vast majority of cases you won’t have to manually delete OMEMO keys or do anything like that. Conversations only introduced the offical backup feature in 2.4.0 after making sure the *OMEMO self healing* mechanism introduced in 2.3.11 works fine. +In the vast, vast majority of cases you won’t have to manually delete OMEMO keys or do anything like that. Conversations only introduced the official backup feature in 2.4.0 after making sure the *OMEMO self healing* mechanism introduced in 2.3.11 works fine. **WARNING**: Be sure to know your accounts passwords or find ways to reset them **before** doing the backup as the files are encrypted using those passwords and the Restore process will ask for them. **WARNING**: Do not use the restore backup feature in an attempt to clone (run simultaneously) an installation. Restoring a backup is only meant for migrations or in case you’ve lost the original device. @@ -327,7 +327,7 @@ OMEMO has two requirements: Your server and the server of your contact need to s OMEMO encryption works only in private (members only) conferences that are non-anonymous. Non-anonymous (being able to discover the real JID of other participants) is a technical requirement to discover the key material. Members only is a sort of arbitrary requirement imposed by Conversations. (see 'OMEMO is grayed out') The server of all participants need to pass the OMEMO [Compliance Test](https://conversations.im/compliance/). -In other words they either need to run Ejabberd 18.01+ or Prosody 0.11+. +In other words they either need to run ejabberd 18.01+ or Prosody 0.11+. (Alternatively it would also work if all participants had each other in their contact list; But that rarely is the case in larger group chats.) diff --git a/build.gradle b/build.gradle index c63d1a14f..365173a6b 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.0.1' + classpath 'com.android.tools.build:gradle:4.1.1' } } @@ -95,9 +95,9 @@ android { defaultConfig { minSdkVersion 21 - targetSdkVersion 28 - versionCode 399 - versionName "2.9.0" + targetSdkVersion 29 + versionCode 402 + versionName "2.9.2" archivesBaseName += "-$versionName" applicationId "eu.sum7.conversations" resValue "string", "applicationId", applicationId diff --git a/fastlane/metadata/android/en-US/changelogs/398.txt b/fastlane/metadata/android/en-US/changelogs/398.txt new file mode 100644 index 000000000..95280ea88 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/398.txt @@ -0,0 +1,4 @@ +* Search individual conversations +* Notify user if message delivery fails +* Remember display names (nicks) from Quicksy users across restarts +* Add button to start Orbot (Tor) from notification if necessary diff --git a/fastlane/metadata/android/en-US/changelogs/401.txt b/fastlane/metadata/android/en-US/changelogs/401.txt new file mode 100644 index 000000000..0fba861d3 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/401.txt @@ -0,0 +1,3 @@ +* fixed search on Android <= 5 +* optimize memory consumption + diff --git a/fastlane/metadata/android/en-US/changelogs/402.txt b/fastlane/metadata/android/en-US/changelogs/402.txt new file mode 100644 index 000000000..53f461756 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/402.txt @@ -0,0 +1,3 @@ +* Offer Easy Invite generation on supporting servers +* Display GIFs send from Movim +* store avatars in cache diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9ad8d1f7c..02d7d70dd 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Sun Jul 26 11:32:42 CEST 2020 +#Sat Nov 14 09:59:55 CET 2020 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip diff --git a/src/conversations/AndroidManifest.xml b/src/conversations/AndroidManifest.xml index 90b78ed4c..62396bed1 100644 --- a/src/conversations/AndroidManifest.xml +++ b/src/conversations/AndroidManifest.xml @@ -20,6 +20,10 @@ android:name=".ui.MagicCreateActivity" android:label="@string/create_new_account" android:launchMode="singleTask" /> + share()); + if (bundle != null && bundle.containsKey("invite")) { + this.easyOnboardingInvite = bundle.getParcelable("invite"); + if (this.easyOnboardingInvite != null) { + showInvite(this.easyOnboardingInvite); + return; + } + } + this.showLoading(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.easy_onboarding_invite, menu); + final MenuItem share = menu.findItem(R.id.action_share); + share.setVisible(easyOnboardingInvite != null); + return super.onCreateOptionsMenu(menu); + } + + public boolean onOptionsItemSelected(MenuItem menuItem) { + if (menuItem.getItemId() == R.id.action_share) { + share(); + return true; + } else { + return super.onOptionsItemSelected(menuItem); + } + } + + private void share() { + final String shareText = getString( + R.string.easy_invite_share_text, + easyOnboardingInvite.getDomain(), + easyOnboardingInvite.getShareableLink() + ); + final Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, shareText); + sendIntent.setType("text/plain"); + startActivity(Intent.createChooser(sendIntent, getString(R.string.share_invite_with))); + } + + @Override + protected void refreshUiReal() { + invalidateOptionsMenu(); + if (easyOnboardingInvite != null) { + showInvite(easyOnboardingInvite); + } else { + showLoading(); + } + } + + private void showLoading() { + this.binding.inProgress.setVisibility(View.VISIBLE); + this.binding.invite.setVisibility(View.GONE); + } + + private void showInvite(final EasyOnboardingInvite invite) { + this.binding.inProgress.setVisibility(View.GONE); + this.binding.invite.setVisibility(View.VISIBLE); + this.binding.tapToShare.setText(getString(R.string.tap_share_button_send_invite, invite.getDomain())); + final Point size = new Point(); + getWindowManager().getDefaultDisplay().getSize(size); + final int width = Math.min(size.x, size.y); + final Bitmap bitmap = BarcodeProvider.create2dBarcodeBitmap(invite.getShareableLink(), width); + binding.qrCode.setImageBitmap(bitmap); + } + + @Override + public void onSaveInstanceState(Bundle bundle) { + super.onSaveInstanceState(bundle); + if (easyOnboardingInvite != null) { + bundle.putParcelable("invite", easyOnboardingInvite); + } + } + + @Override + void onBackendConnected() { + if (easyOnboardingInvite != null) { + return; + } + final Intent launchIntent = getIntent(); + final String accountExtra = launchIntent.getStringExtra(EXTRA_ACCOUNT); + final Jid jid = accountExtra == null ? null : Jid.ofEscaped(accountExtra); + if (jid == null) { + return; + } + final Account account = xmppConnectionService.findAccountByJid(jid); + xmppConnectionService.requestEasyOnboardingInvite(account, this); + } + + public static void launch(final Account account, final Activity context) { + final Intent intent = new Intent(context, EasyOnboardingInviteActivity.class); + intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString()); + context.startActivity(intent); + } + + @Override + public void inviteRequested(EasyOnboardingInvite invite) { + this.easyOnboardingInvite = invite; + Log.d(Config.LOGTAG, "invite requested"); + refreshUi(); + } + + @Override + public void inviteRequestFailed(final String message) { + runOnUiThread(() -> { + if (!Strings.isNullOrEmpty(message)) { + Toast.makeText(this, message, Toast.LENGTH_LONG).show(); + } + finish(); + }); + } +} diff --git a/src/conversations/res/layout/activity_easy_invite.xml b/src/conversations/res/layout/activity_easy_invite.xml new file mode 100644 index 000000000..8bbf11c03 --- /dev/null +++ b/src/conversations/res/layout/activity_easy_invite.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + +