From 582b36bc803f5dbc46076bb1f1b5520fc9784ccd Mon Sep 17 00:00:00 2001 From: Balazs Toldi Date: Wed, 17 Feb 2021 15:51:24 +0100 Subject: [PATCH 01/35] Open gemini link from external application --- .../java/app/fedilab/android/client/Entities/Status.java | 9 ++------- .../main/java/app/fedilab/android/helper/BaseHelper.java | 2 +- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/client/Entities/Status.java b/app/src/main/java/app/fedilab/android/client/Entities/Status.java index 55a546dd2..e00399538 100644 --- a/app/src/main/java/app/fedilab/android/client/Entities/Status.java +++ b/app/src/main/java/app/fedilab/android/client/Entities/Status.java @@ -665,9 +665,7 @@ public class Status implements Parcelable { intent.putExtras(b); context.startActivity(intent); } else { - if (!url.toLowerCase().startsWith("http://") && !url.toLowerCase().startsWith("https://")) - finalUrl = "http://" + url; - Helper.openBrowser(context, finalUrl); + Helper.openBrowser(context, url); } } @@ -891,10 +889,7 @@ public class Status implements Parcelable { contentSpanTranslated.setSpan(new ClickableSpan() { @Override public void onClick(@NonNull View textView) { - String finalUrl = url; - if (!url.toLowerCase().startsWith("http://") && !url.toLowerCase().startsWith("https://")) - finalUrl = "http://" + url; - Helper.openBrowser(context, finalUrl); + Helper.openBrowser(context, url); } @Override diff --git a/app/src/main/java/app/fedilab/android/helper/BaseHelper.java b/app/src/main/java/app/fedilab/android/helper/BaseHelper.java index 0bb588585..aa657fc16 100644 --- a/app/src/main/java/app/fedilab/android/helper/BaseHelper.java +++ b/app/src/main/java/app/fedilab/android/helper/BaseHelper.java @@ -3166,7 +3166,7 @@ public class BaseHelper { public static void openBrowser(Context context, String url) { SharedPreferences sharedpreferences = context.getSharedPreferences(APP_PREFS, android.content.Context.MODE_PRIVATE); boolean embedded_browser = sharedpreferences.getBoolean(SET_EMBEDDED_BROWSER, true); - if (embedded_browser) { + if (embedded_browser && !url.toLowerCase().startsWith("gemini://")) { Intent intent = new Intent(context, WebviewActivity.class); Bundle b = new Bundle(); String finalUrl = url; From e4dc18ff6b4f19d6258b3bd524b5fe2cf8696b12 Mon Sep 17 00:00:00 2001 From: Balazs Toldi Date: Wed, 17 Feb 2021 17:34:10 +0100 Subject: [PATCH 02/35] Added gemini link activation for account metadata --- .../android/client/Entities/Account.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/app/src/main/java/app/fedilab/android/client/Entities/Account.java b/app/src/main/java/app/fedilab/android/client/Entities/Account.java index ee0eb60ef..a57dd3c33 100644 --- a/app/src/main/java/app/fedilab/android/client/Entities/Account.java +++ b/app/src/main/java/app/fedilab/android/client/Entities/Account.java @@ -828,6 +828,37 @@ public class Account implements Parcelable { } }, matchStart, matchEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); } + matcher = Pattern.compile("gemini://([\\d\\w.-]*)(:\\d+)?(/\\S*)?").matcher(fieldSpan); + while (matcher.find()) { + URLSpan[] urls = fieldSpan.getSpans(0, fieldSpan.length(), URLSpan.class); + for (URLSpan span : urls) + fieldSpan.removeSpan(span); + int matchStart = matcher.start(0); + int matchEnd = matcher.end(); + final String url = fieldSpan.toString().substring(matchStart, matchEnd); + if (matchStart >= 0 && matchEnd <= fieldSpan.toString().length() && matchEnd >= matchStart) { + fieldSpan.setSpan(new ClickableSpan() { + @Override + public void onClick(@NonNull View textView) { + try { + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + context.startActivity(intent); + } catch (Exception e) { + Toasty.error(context, context.getString(R.string.toast_no_apps), Toast.LENGTH_LONG).show(); + } + } + + @Override + public void updateDrawState(@NonNull TextPaint ds) { + super.updateDrawState(ds); + ds.setUnderlineText(false); + ds.setColor(link_color); + } + }, matchStart, matchEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + fieldsSpan.put(keySpan, fieldSpan); + } + + } if (accountsMentionUnknown.size() > 0) { for (Account accountMention : accountsMentionUnknown) { String targetedAccount = "@" + accountMention.getAcct(); From b7fdd58df56b038fc59f63b66715e04f2a3af19c Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 19 Feb 2021 14:31:50 +0100 Subject: [PATCH 03/35] New Crowdin updates --- app/src/main/res/values-ar/strings.xml | 3 +++ app/src/main/res/values-ber/strings.xml | 3 +++ app/src/main/res/values-bn/strings.xml | 3 +++ app/src/main/res/values-br/strings.xml | 3 +++ app/src/main/res/values-ca/strings.xml | 3 +++ app/src/main/res/values-cs/strings.xml | 3 +++ app/src/main/res/values-cy/strings.xml | 3 +++ app/src/main/res/values-da/strings.xml | 3 +++ app/src/main/res/values-de/strings.xml | 3 +++ app/src/main/res/values-el/strings.xml | 3 +++ app/src/main/res/values-eo/strings.xml | 3 +++ app/src/main/res/values-es/strings.xml | 3 +++ app/src/main/res/values-eu/strings.xml | 3 +++ app/src/main/res/values-fa/strings.xml | 3 +++ app/src/main/res/values-fr/strings.xml | 3 +++ app/src/main/res/values-gl/strings.xml | 3 +++ app/src/main/res/values-hi/strings.xml | 3 +++ app/src/main/res/values-hu/strings.xml | 3 +++ app/src/main/res/values-hy/strings.xml | 3 +++ app/src/main/res/values-id/strings.xml | 3 +++ app/src/main/res/values-it/strings.xml | 3 +++ app/src/main/res/values-ja/strings.xml | 3 +++ app/src/main/res/values-kab/strings.xml | 3 +++ app/src/main/res/values-ko/strings.xml | 3 +++ app/src/main/res/values-ml/strings.xml | 3 +++ app/src/main/res/values-nl/strings.xml | 3 +++ app/src/main/res/values-no/strings.xml | 3 +++ app/src/main/res/values-oc/strings.xml | 3 +++ app/src/main/res/values-pl/strings.xml | 3 +++ app/src/main/res/values-pt/strings.xml | 3 +++ app/src/main/res/values-ro/strings.xml | 3 +++ app/src/main/res/values-ru/strings.xml | 3 +++ app/src/main/res/values-sc/strings.xml | 3 +++ app/src/main/res/values-si/strings.xml | 3 +++ app/src/main/res/values-sl/strings.xml | 3 +++ app/src/main/res/values-sr/strings.xml | 3 +++ app/src/main/res/values-sv/strings.xml | 3 +++ app/src/main/res/values-szl/strings.xml | 3 +++ app/src/main/res/values-tr/strings.xml | 3 +++ app/src/main/res/values-uk/strings.xml | 3 +++ app/src/main/res/values-vi/strings.xml | 3 +++ app/src/main/res/values-zh-rCN/strings.xml | 3 +++ app/src/main/res/values-zh-rTW/strings.xml | 3 +++ 43 files changed, 129 insertions(+) diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index dcf554ddc..ebe65316b 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -1038,6 +1038,9 @@ استبدال Instagram بـ Bibliogram Bibliogram هو بديل مفتوح المصدر لـ Instagram يركز على الخصوصية. أدخل مضيفك المخصص أو اتركه فارغًا لاستخدام bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it أخفِ شريط إشعارات Fedilab لإخفاء الإشعارات المتبقية على شريط الحالة ، اضغط على زر أيقونة العين ثم قم بإلغاء تحديد: \"العرض على شريط الحالة\" سيتم تأجيل الإشعارات كل 30 ثانية. الشيء الذي من شأنه أن يسمح باستنزاف أقل للبطارية. diff --git a/app/src/main/res/values-ber/strings.xml b/app/src/main/res/values-ber/strings.xml index 9a1f819e3..ec602b027 100644 --- a/app/src/main/res/values-ber/strings.xml +++ b/app/src/main/res/values-ber/strings.xml @@ -1005,6 +1005,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index a67bead13..f72745b02 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -1007,6 +1007,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-br/strings.xml b/app/src/main/res/values-br/strings.xml index 4aec5de5e..937136b65 100644 --- a/app/src/main/res/values-br/strings.xml +++ b/app/src/main/res/values-br/strings.xml @@ -1031,6 +1031,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index e0273f6fe..4cbadf16e 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -997,6 +997,9 @@ Ara ja pots connectar-te al compte escrivint %1$s en el primer camp i fen Substituir Instagram per Bibliogram Bibliogram és un frontal alternatiu de codi obert per Instagram que protegeix la privacitat. Introdueix aquí el teu servidor personalitzat o deixa-ho en blanc i s\'usarà bibliogram.art + Substituïu Reddit per Libreddit + Libreddit és una interfície alternativa a Reddit, és de codi obert i prioritza la privadesa. + Introduïu el vostre servidor personalitzat o deixeu-ho em blanc per utilitzar libredd.it Amaga la barra de notificacions de Fedilab Per amagar la notificació que queda a la barra d\'estat, toca la icona de l\'ull i després desactiva: \"Mostra a la barra d\'estat\" S\'ajornaran les notificacions cada 30 segons. Això permet gastar menys la pila. diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 9c37afec8..de6e2f2c0 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -1021,6 +1021,9 @@ Uživatelské jméno a heslo nejsou nikdy ukládány. Jsou použity pouze během Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-cy/strings.xml b/app/src/main/res/values-cy/strings.xml index a6d3402f6..770ee512c 100644 --- a/app/src/main/res/values-cy/strings.xml +++ b/app/src/main/res/values-cy/strings.xml @@ -1038,6 +1038,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index ea8730614..357022be4 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -1007,6 +1007,9 @@ Erstat Instagram med Bibliogram Bibliogram er et open source Instagram front-end alternativt fokuseret på datafortrolighed. Angiv din tilpassede vært eller lad stå tomt for at benytte bibliogram.art + Erstat Reddit med Libreddit + Libreddit er en fortrolighedorienteret, alternativ open-source Twitter front-end. + Angiv din tilpassede vært eller lad stå tomt for brug af libredd.it Skjul Fedilab-notifikationsbjælke For at skjule den resterende notifikation på statusbjælken, så klik på øjeikonknappen og fjern derefter markeringen: \"Vises på statusbjælke\" Notifikationer udskydes hvert 30. sekund. Dette vil betyde lavere batteridræning. diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 855f4a377..1aa416c7c 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -995,6 +995,9 @@ Sobald du die ersten Buchstaben eintippst, werden Namensvorschläge angezeigt\n\ Instagram durch Bibliogram ersetzen Bibliogram ist ein alternatives Open-Source-Frontend von Instagram, das sich auf den Datenschutz konzentriert. Geben Sie Ihren benutzerdefinierten Host ein oder lassen Sie das Feld für die Verwendung von bibliogram.art frei + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Fedilab Benachrichtigungsleiste ausblenden Um die verbleibende Benachrichtigung in der Statusleiste zu verstecken, klicken Sie auf die Augensymbol-Schaltfläche und deaktivieren Sie: \"In Statusleiste anzeigen\" Benachrichtigungen werden 30 Sekunden verzögert. Dadurch wird weniger Akku verbraucht. diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 36cecd254..e5b57a634 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -1007,6 +1007,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Απόκρυψη της γραμμής ειδοποιήσεων του Φέντιλαμπ Για απόκρυψη της υπόλοιπης ειδοποίησης στη γραμμή κατάστασης, πίεσε στο πλήκτρο με το μάτι, και μετά απο-επέλεξε την...: «Προβολή στη γραμμή κατάστασης» Οι ειδοποιήσεις θα καθυστερούν κάθε 30 δευτερόλεπτα. Αυτό θα βοηθήσει στην χαμηλότερη απομύζηση της μπαταρίας. diff --git a/app/src/main/res/values-eo/strings.xml b/app/src/main/res/values-eo/strings.xml index a006323cb..f1097a348 100644 --- a/app/src/main/res/values-eo/strings.xml +++ b/app/src/main/res/values-eo/strings.xml @@ -1007,6 +1007,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 7a8edecf2..c7639d9f5 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -1001,6 +1001,9 @@ https://yandex.ru/legal/confidential/?lang=en Reemplazar Instagram con Bibliogram Bibliogram es una interfaz alternativa de Instagram de código abierto centrada en la privacidad. Introduzca su servidor personalizado o déjelo en blanco para usar bibliogram.art + Reemplazar Reddit con Libreddit + Libreddit es una interfaz alternativa de código abierto de Reddit enfocada en la privacidad. + Introduce tu servidor personalizado o déjalo en blanco para usar libredd.it Ocultar la barra de notificaciones de Fedilab Para ocultar el resto de la notificación en la barra de estado, presione en el botón con el ícono de un ojo y luego desactivar \"Mostrar en la barra de estado\" Las notificaciones se retrasarán cada 30 segundos. Esto permitirá gastar menos batería. diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 1875fb203..9475083ef 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -1006,6 +1006,9 @@ Ordeztu Instagram Bibliogram-ekin Bibliogram pribatutasuna aintzat duen Instagram interfaze libre bat da. Sartu zure ostalari pertsonalizatua edo laga hutsik bibliogram.art erabiltzeko + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Ezkutatu Fedilab jakinarazpen-barra Egoera barran geratzen den jakinarazpena ezkutatzeko, sakatu begiaren ikonoa eta desmarkatu \"Erakutsi egoera-barran\" Jakinarazpenak 30 segundo atzeratuko dira. Honela bateria gutxiago erabiliko da. diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 6cbff0c2c..5ec13c8e8 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -1007,6 +1007,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index edb9826ac..98a918ba6 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -1004,6 +1004,9 @@ Le bouton de connexion s’activera une fois qu’un domaine valide sera renseig Remplacer Instagram par Bibliogram Bibliogram est une interface ouverte en alternative à Instagram axée sur la confidentialité. Entrez votre hôte personnalisé ou laissez vide pour utiliser bibliogram.art + Remplacer Reddit par Libreddit + Libreddit est une alternative ouverte à l\'interface de Reddit axée sur la protection de la vie privée. + Entrez votre hôte personnalisé ou laissez vide pour utiliser libredd.it Masquer la barre de notification de Fedilab Pour cacher le reste de la notification dans la barre de statut, cliquez sur le bouton en forme d\'œil, puis décochez: \"Afficher dans la barre de statut\" Les notifications seront mises à jour toutes les 30 secondes. Cela permet de réduire la consommation de la batterie. diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 40dbd0069..f454efe6c 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -1007,6 +1007,9 @@ Substituír Instagram con Bibliogram Bibliogram é unha alternativa de código aberto centrada na privacidade a interface de Instagram. Escribe o servidor personalizado ou deixa en branco para usar bibliogram.art + Substituir Reddit por Libreddit + Libreddit é unha alternativa de código aberto á interface de Reddit centrada na privacidade. + Escribe o teu servidor personalizado ou deixa baleiro para usar libredd.it Ocultar barra de notificacións de Fedilab Para ocultar as notificacións remanentes na barra de estado, pulsa na icona do ollo e desmarca: \"Mostrar en barra de estado\" As notificacións retrasaranse cada 30 segundos. Esto permite aforrar batería. diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 8bd4dd36c..a07ec3ff6 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -999,6 +999,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 2314c6e5d..de49581c4 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -1005,6 +1005,9 @@ A Yandexnek megvan a saját adatvédelmi szabályzata, ami itt található: http Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-hy/strings.xml b/app/src/main/res/values-hy/strings.xml index e99f11e33..bfc37fb13 100644 --- a/app/src/main/res/values-hy/strings.xml +++ b/app/src/main/res/values-hy/strings.xml @@ -1006,6 +1006,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index 2c10e7436..78ef65a08 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -1001,6 +1001,9 @@ https://yandex.ru/legal/confidential/?lang=en Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 2d1b98d38..f80bb7a16 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -1007,6 +1007,9 @@ Sostituisci Instagram con Bibliogram Bibliogram è un\'interfaccia alternativa e open source per Instagram, focalizzata sulla privacy. Inserisci il tuo host personalizzato o lascia vuoto per usare bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Nascondi barra di notifica di Fedilab Per nascondere le notifiche rimanenti nella barra di stato, premi il pulsante con l\'icona di un occhio e deseleziona: \"Mostra nella barra di stato\" Le notifiche saranno differite ogni 30 secondi. Questo consentirà un minore consumo di batteria. diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 00cc800a1..8f317af85 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -990,6 +990,9 @@ InstagramをBibliogramに置き換える Bibliogramはプライバシーを重視したオープンソースのInstagramの代替フロントエンドです。 カスタムホストを入力してください。空欄にするとbibliogram.artが使用されます。 + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it 通知バーからFedilabを非表示にする ステータスバー上の通知を非表示にするには、目のアイコンボタンをタップして「ステータスバーに表示する」のチェックを外します 通知は30秒ごとに遅延が発生します。バッテリーの持ちが改善します。 diff --git a/app/src/main/res/values-kab/strings.xml b/app/src/main/res/values-kab/strings.xml index 4cdbaa019..54bcf9782 100644 --- a/app/src/main/res/values-kab/strings.xml +++ b/app/src/main/res/values-kab/strings.xml @@ -1007,6 +1007,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 2b01e6283..5216ea768 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -999,6 +999,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index fc52acdc8..9737cb1fe 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -1005,6 +1005,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index b043050da..e608c232d 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -1006,6 +1006,9 @@ Je kunt beginnen met typen en er zullen namen gesuggereerd worden.\n\n Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index ff3d7affc..0bb1891a8 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -997,6 +997,9 @@ Adresser vil bli foreslått når du begynner å skrive.\n\n Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-oc/strings.xml b/app/src/main/res/values-oc/strings.xml index d463b31db..a3d76011e 100644 --- a/app/src/main/res/values-oc/strings.xml +++ b/app/src/main/res/values-oc/strings.xml @@ -1007,6 +1007,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 1435945d1..e21305bf1 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -1020,6 +1020,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index fecdf65cd..1d661884e 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -1007,6 +1007,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index ab6248538..f2a00db65 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -1013,6 +1013,9 @@ Aceste date sunt strict confidențiale și pot fi folosite doar de aplicație. Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index ba4798d13..ab249b41e 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -1023,6 +1023,9 @@ Заменить Instagram на Bibliogram Bibliogram это альтернативный front-end для Instagram с открытым исходным кодом и сфокусированный на приватности. Введите свой собственный хост или оставьте пустым для использования bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Скрыть панель уведомлений Fedilab Чтобы скрыть уведомление в строке состояния, нажмите на кнопку с пиктограммой глаза и снимите флажок: \"Показывать в строке состояния\" Уведомления будут появляться на 30 секунд позже. Это позволит уменьшить влияние на батарею. diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index 7d42faaa1..2835c3a31 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -970,6 +970,9 @@ Remplasa Instagram cun Bibliogram Bibliogram est un\'interfache alternativa pro Instagram, a mitza aberta e progetada pro sa riservadesa. Inserta s\'istrangiadore (host) personalizadu tuo o lassa bòidu pro bibliogram.art + Remplasa Reddit cun Libreddit + Libreddit est un\'interfache alternativa a mitza aberta pro Reddit progetada pro sa riservadesa. + Inserta s\'istrangiadore (host) personalizadu tuo o lassa bòidu pro impreare libredd.it Cua sa barra de is notìficas de Fedilab Pro cuare is notìficas chi abarrant in sa barra de istadu toca in su butone cun s\'icona a forma de ogru e boga sa seletzione a: \"Ammustra in sa barra de istadu\" Is notìficas ant a èssere perlongadas cada 30 segundos. Custu at a permìtere de consumire de mancu sa bateria. diff --git a/app/src/main/res/values-si/strings.xml b/app/src/main/res/values-si/strings.xml index e610a2635..b3770d7e9 100644 --- a/app/src/main/res/values-si/strings.xml +++ b/app/src/main/res/values-si/strings.xml @@ -1007,6 +1007,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml index 946744b31..e7a24b6bd 100644 --- a/app/src/main/res/values-sl/strings.xml +++ b/app/src/main/res/values-sl/strings.xml @@ -1023,6 +1023,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml index 6b574837f..6cc1f77d1 100644 --- a/app/src/main/res/values-sr/strings.xml +++ b/app/src/main/res/values-sr/strings.xml @@ -1015,6 +1015,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 16e023810..d2425fca8 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -1007,6 +1007,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Dölj Fedilab meddelandefält För att dölja kvarvarande notifieringar i statusbaren, klicka på ögonikons knappen och kryssa ur: \"Visa i statusbar\" Notifieringar kommer fördröjas var 30:e sekund. Detta kommer att dra mindre ström. diff --git a/app/src/main/res/values-szl/strings.xml b/app/src/main/res/values-szl/strings.xml index bb03e6017..2321ddacb 100644 --- a/app/src/main/res/values-szl/strings.xml +++ b/app/src/main/res/values-szl/strings.xml @@ -1021,6 +1021,9 @@ Zamiyń Instagram na Bibliogram Bibliogram to ôtwartozdrzōdłowo, skupiōno na prywatności alternatywa do przodnich funkcyji Instagrama. Wkludź swōj włosny host abo ôstow prōzne, żeby używać bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Skryj posek powiadōmiyń Fedilab Żeby skryć permanyntne powiadōmiynie we posku statusu, tyknij knefel z ôkym i ôdznacz „Pokoż we posku statusu” Powiadōmiynia bydōm ôpōźniōne co 30 sekund. To przizwoli na myńsze wyużycie bateryje. diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 9874c96f9..3d5f12c90 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -1001,6 +1001,9 @@ Instagram\'ı Bibliogram ile Değiştirin Bibliogram, mahremiyete odaklanan açık kaynaklı bir alternatif Instagram arayüzdür. Enter your custom host or leave blank for using bibliogram.art + Reddit yerine Libreddit kullan + Libreddit, mahremiyete odaklanan açık kaynaklı bir alternatif Reddit arayüzüdür. + Sunucu adresi girin ya da libredd.it sunucusunu kullanmak için boş bırakın Fedilab bildirim çubuğunu gizle Durum çubuğunda kalan bildirimi gizlemek için, göz simgesine dokunun ve ardından \"Durum çubuğunda göster\" seçeneğinin işaretini kaldırın Bildirimler 30 saniye geciktirilecek. Bu sayede pil harcaması azalacak. diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 1494e4791..03d767708 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -1013,6 +1013,9 @@ Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index e13158ff5..406945ade 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -996,6 +996,9 @@ và %d thông báo khác Replace Instagram with Bibliogram Bibliogram is an open source alternative Instagram front-end focused on privacy. Enter your custom host or leave blank for using bibliogram.art + Replace Reddit with Libreddit + Libreddit is an open source alternative Reddit front-end focused on privacy. + Enter your custom host or leave blank for using libredd.it Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" Notifications will be delayed every 30 seconds. That will allow to drain less battery. diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index e01abc672..104e8f57e 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -999,6 +999,9 @@ 将 Instagram 替换成 Bibliogram Bibliogram 是个注重隐私保护的 Instagram 开源替代前端。 输入你的自定义主机或者留空以使用 bibliogram.art + 用 Libreddit 替换 Reddit + Libreddit 是一个开源的 Reddit 替代前端,专注于隐私。 + 输入您的自定义主机或留空使用 libredd.it 隐藏 Fedilab 通知栏 要隐藏留在状态栏里的通知,请点击眼睛图标的按钮,然后取消勾选:“在状态栏内显示” 通知将被延迟为每30秒产生。这将允许耗尽较少电池。 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index a47bc69a0..96ce51bff 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -998,6 +998,9 @@ Yandex 有適當的隱私權政策,可以在這裡找到:https://yandex.ru/l 將 Instagram 以 Bibliogram 取代 Bibliogram 是一個尊重隱私的開放原始碼替代 Instagram 前端。 輸入您的自訂主機或留空以使用 bibliogram.art + 用 Libreddit 取代 Reddit + Libreddit 是一個尊重隱私的開放原始碼替代 Reddit 前端。 + 輸入您的自訂主機或留空以使用 libredd.it 隱藏 Fedilab 通知列 要隱藏狀態列中的其餘通知,點擊眼睛圖示的按鈕上然後取消勾選「在狀態列中顯示」 通知將會每30秒被延遲一次。這將會降低電池的消耗。 From f7702ecc12d89d4931149b24b8a15a4a638bf92b Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 21 Feb 2021 17:18:37 +0100 Subject: [PATCH 04/35] unifiedpush - first changes --- app/build.gradle | 5 + app/src/common/AndroidManifest.xml | 15 ++ app/src/main/AndroidManifest.xml | 12 ++ .../android/activities/BaseMainActivity.java | 18 +- .../asynctasks/PostSubscriptionAsyncTask.java | 51 +++++ .../java/app/fedilab/android/client/API.java | 82 ++++++++ .../fedilab/android/client/APIResponse.java | 10 + .../client/Entities/PushSubscription.java | 57 ++++++ .../android/client/HttpsConnection.java | 57 ++++++ .../drawers/BaseStatusListAdapter.java | 21 +- .../fedilab/android/helper/BaseHelper.java | 4 +- .../java/app/fedilab/android/helper/ECDH.java | 182 ++++++++++++++++++ .../android/helper/PushNotifications.java | 32 +++ .../interfaces/OnPostSubscription.java | 21 ++ .../android/services/UnifiedPushService.java | 45 +++++ app/src/main/res/values/colors.xml | 9 + build.gradle | 3 +- 17 files changed, 618 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java create mode 100644 app/src/main/java/app/fedilab/android/client/Entities/PushSubscription.java create mode 100644 app/src/main/java/app/fedilab/android/helper/ECDH.java create mode 100644 app/src/main/java/app/fedilab/android/helper/PushNotifications.java create mode 100644 app/src/main/java/app/fedilab/android/interfaces/OnPostSubscription.java create mode 100644 app/src/main/java/app/fedilab/android/services/UnifiedPushService.java diff --git a/app/build.gradle b/app/build.gradle index b4f3360cf..686d93680 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -173,6 +173,11 @@ dependencies { //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2' implementation 'com.huangyz0918:androidwm-light:0.1.2' + implementation "com.madgag.spongycastle:bctls-jdk15on:1.58.0.0" + implementation "com.madgag.spongycastle:prov:1.58.0.0" + implementation "com.madgag.spongycastle:bcpkix-jdk15on:1.58.0.0" + implementation "com.madgag.spongycastle:bcpg-jdk15on:1.58.0.0" + implementation 'com.github.UnifiedPush:android-connector:1.0.0' //Flavors //Playstore diff --git a/app/src/common/AndroidManifest.xml b/app/src/common/AndroidManifest.xml index d08f233c2..397a7c259 100644 --- a/app/src/common/AndroidManifest.xml +++ b/app/src/common/AndroidManifest.xml @@ -84,5 +84,20 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 140b7d0cb..31711a6bf 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -460,6 +460,18 @@ android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java index 862e18587..99f48562f 100644 --- a/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java @@ -35,6 +35,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.Parcelable; import android.preference.PreferenceManager; +import android.util.Log; import android.util.Patterns; import android.view.LayoutInflater; import android.view.Menu; @@ -95,6 +96,7 @@ import java.util.regex.Matcher; import app.fedilab.android.BuildConfig; import app.fedilab.android.R; import app.fedilab.android.asynctasks.ManageFiltersAsyncTask; +import app.fedilab.android.asynctasks.PostSubscriptionAsyncTask; import app.fedilab.android.asynctasks.RetrieveAccountsAsyncTask; import app.fedilab.android.asynctasks.RetrieveFeedsAsyncTask; import app.fedilab.android.asynctasks.RetrieveInstanceAsyncTask; @@ -112,6 +114,7 @@ import app.fedilab.android.client.Entities.Error; import app.fedilab.android.client.Entities.Filters; import app.fedilab.android.client.Entities.Instance; import app.fedilab.android.client.Entities.ManageTimelines; +import app.fedilab.android.client.Entities.PushSubscription; import app.fedilab.android.client.Entities.Relationship; import app.fedilab.android.client.Entities.Results; import app.fedilab.android.client.Entities.Status; @@ -137,6 +140,7 @@ import app.fedilab.android.helper.ExpandableHeightListView; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.MenuFloating; import app.fedilab.android.interfaces.OnFilterActionInterface; +import app.fedilab.android.interfaces.OnPostSubscription; import app.fedilab.android.interfaces.OnRetrieveEmojiAccountInterface; import app.fedilab.android.interfaces.OnRetrieveFeedsInterface; import app.fedilab.android.interfaces.OnRetrieveInstanceInterface; @@ -161,7 +165,7 @@ import static app.fedilab.android.helper.Helper.changeDrawableColor; public abstract class BaseMainActivity extends BaseActivity - implements NavigationView.OnNavigationItemSelectedListener, OnRetrieveFeedsInterface, OnUpdateAccountInfoInterface, OnRetrieveMetaDataInterface, OnRetrieveInstanceInterface, OnRetrieveRemoteAccountInterface, OnRetrieveEmojiAccountInterface, OnFilterActionInterface, OnSyncTimelineInterface, OnRetrieveRelationshipInterface { + implements NavigationView.OnNavigationItemSelectedListener, OnRetrieveFeedsInterface, OnUpdateAccountInfoInterface, OnRetrieveMetaDataInterface, OnRetrieveInstanceInterface, OnRetrieveRemoteAccountInterface, OnRetrieveEmojiAccountInterface, OnFilterActionInterface, OnSyncTimelineInterface, OnRetrieveRelationshipInterface, OnPostSubscription { public static String currentLocale; @@ -249,6 +253,7 @@ public abstract class BaseMainActivity extends BaseActivity e.printStackTrace(); } + if (account == null) { Helper.logoutCurrentUser(BaseMainActivity.this); Intent myIntent = new Intent(BaseMainActivity.this, LoginActivity.class); @@ -417,6 +422,12 @@ public abstract class BaseMainActivity extends BaseActivity add_new = findViewById(R.id.add_new); main_app_container = findViewById(R.id.main_app_container); + + + Log.v(Helper.TAG, "social: " + social); + if (social == UpdateAccountInfoAsyncTask.SOCIAL.MASTODON || social == UpdateAccountInfoAsyncTask.SOCIAL.PLEROMA) { + new PostSubscriptionAsyncTask(BaseMainActivity.this, this); + } if (social == UpdateAccountInfoAsyncTask.SOCIAL.MASTODON || social == UpdateAccountInfoAsyncTask.SOCIAL.PLEROMA || social == UpdateAccountInfoAsyncTask.SOCIAL.GNU || social == UpdateAccountInfoAsyncTask.SOCIAL.FRIENDICA) { new SyncTimelinesAsyncTask(BaseMainActivity.this, 0, Helper.canFetchList(BaseMainActivity.this, account), BaseMainActivity.this); @@ -2421,6 +2432,11 @@ public abstract class BaseMainActivity extends BaseActivity protected abstract void launchOwnerNotificationsActivity(); + @Override + public void onSubscription(APIResponse apiResponse) { + + } + public enum iconLauncher { BUBBLES, FEDIVERSE, diff --git a/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java b/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java new file mode 100644 index 000000000..aca5ff329 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java @@ -0,0 +1,51 @@ +/* Copyright 2021 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ +package app.fedilab.android.asynctasks; + +import android.content.Context; +import android.os.Handler; +import android.os.Looper; + +import java.lang.ref.WeakReference; + +import app.fedilab.android.client.API; +import app.fedilab.android.client.APIResponse; +import app.fedilab.android.interfaces.OnPostSubscription; + + +public class PostSubscriptionAsyncTask { + + private final OnPostSubscription listener; + private final WeakReference contextReference; + private APIResponse apiResponse; + + + public PostSubscriptionAsyncTask(Context context, OnPostSubscription onPostSubscription) { + this.contextReference = new WeakReference<>(context); + this.listener = onPostSubscription; + doInBackground(); + } + + + protected void doInBackground() { + new Thread(() -> { + apiResponse = new API(contextReference.get()).pushSubscription(); + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> listener.onSubscription(apiResponse); + mainHandler.post(myRunnable); + }).start(); + } + +} diff --git a/app/src/main/java/app/fedilab/android/client/API.java b/app/src/main/java/app/fedilab/android/client/API.java index dd312924c..47e8f77dd 100644 --- a/app/src/main/java/app/fedilab/android/client/API.java +++ b/app/src/main/java/app/fedilab/android/client/API.java @@ -22,9 +22,12 @@ import android.os.Build; import android.os.Bundle; import android.text.Html; import android.text.SpannableString; +import android.util.Base64; +import android.util.Log; import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; import com.google.gson.JsonObject; @@ -46,6 +49,7 @@ import java.net.URLEncoder; import java.net.UnknownHostException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; +import java.security.Security; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -58,6 +62,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; +import java.util.Random; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -88,6 +93,7 @@ import app.fedilab.android.client.Entities.Notification; import app.fedilab.android.client.Entities.Peertube; import app.fedilab.android.client.Entities.Poll; import app.fedilab.android.client.Entities.PollOptions; +import app.fedilab.android.client.Entities.PushSubscription; import app.fedilab.android.client.Entities.Reaction; import app.fedilab.android.client.Entities.Relationship; import app.fedilab.android.client.Entities.Report; @@ -99,7 +105,9 @@ import app.fedilab.android.client.Entities.Tag; import app.fedilab.android.client.Entities.Trends; import app.fedilab.android.client.Entities.TrendsHistory; import app.fedilab.android.fragments.DisplayNotificationsFragment; +import app.fedilab.android.helper.ECDH; import app.fedilab.android.helper.Helper; +import app.fedilab.android.helper.PushNotifications; import app.fedilab.android.sqlite.AccountDAO; import app.fedilab.android.sqlite.Sqlite; import app.fedilab.android.sqlite.TimelineCacheDAO; @@ -555,6 +563,34 @@ public class API { return reactions; } + + /** + * Parse a push notification + * + * @param resobj JSONObject + * @return PushSubscription + */ + private static PushSubscription parsePushNotifications(JSONObject resobj) { + PushSubscription pushSubscription = new PushSubscription(); + try { + pushSubscription.setId(resobj.getString("id")); + pushSubscription.setEndpoint(resobj.getString("endpoint")); + pushSubscription.setServer_key(resobj.getString("server_key")); + JSONObject alertsObject = resobj.getJSONObject("alerts"); + Iterator iter = alertsObject.keys(); + HashMap alertsList = new HashMap<>(); + while (iter.hasNext()) { + String key = iter.next(); + boolean value = (boolean) alertsObject.get(key); + alertsList.put(key, value); + } + pushSubscription.setAlertsList(alertsList); + } catch (JSONException e) { + e.printStackTrace(); + } + return pushSubscription; + } + /** * Parse a reaction * @@ -5745,6 +5781,52 @@ public class API { return apiResponse; } + /** + * Subscribe to push notifications + * + * @return APIResponse + */ + public APIResponse pushSubscription() { + PushSubscription pushSubscription = new PushSubscription(); + final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true); + boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true); + boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true); + boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true); + boolean notif_status = sharedpreferences.getBoolean(Helper.SET_NOTIF_STATUS, true); + boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true); + + HashMap params = new HashMap<>(); + params.put("data[alerts][follow]", String.valueOf(notif_follow)); + params.put("data[alerts][mention]", String.valueOf(notif_mention)); + params.put("data[alerts][favourite]", String.valueOf(notif_add)); + params.put("data[alerts][reblog]", String.valueOf(notif_share)); + params.put("data[alerts][poll]", String.valueOf(notif_poll)); + + params.put("subscription[endpoint]", getAbsoluteUrl("/streaming/user")); + + ECDH ecdh = ECDH.getInstance(); + String pubKey = ecdh.getPublicKey(context); + byte[] randBytes = new byte[16]; + new Random().nextBytes(randBytes); + String auth = Base64.encodeToString(randBytes, Base64.DEFAULT); + params.put("subscription[keys][p256dh]", pubKey); + params.put("subscription[keys][auth]", auth); + Log.v(Helper.TAG, "params: " + params); + try { + String response = new HttpsConnection(context, this.instance).post(getAbsoluteUrl("/push/subscription"), 10, params, prefKeyOauthTokenT); + Log.v(Helper.TAG, "response: " + response); + pushSubscription = parsePushNotifications(new JSONObject(response)); + } catch (HttpsConnection.HttpsConnectionException e) { + setError(e.getStatusCode(), e); + e.printStackTrace(); + } catch (NoSuchAlgorithmException | IOException | KeyManagementException | JSONException e) { + e.printStackTrace(); + } + apiResponse.setPushSubscription(pushSubscription); + return apiResponse; + } + /** * Update a list by its id * diff --git a/app/src/main/java/app/fedilab/android/client/APIResponse.java b/app/src/main/java/app/fedilab/android/client/APIResponse.java index 635eaeed2..62327c10e 100644 --- a/app/src/main/java/app/fedilab/android/client/APIResponse.java +++ b/app/src/main/java/app/fedilab/android/client/APIResponse.java @@ -35,6 +35,7 @@ import app.fedilab.android.client.Entities.PeertubeNotification; import app.fedilab.android.client.Entities.PixelFedStory; import app.fedilab.android.client.Entities.PixelFedStoryItem; import app.fedilab.android.client.Entities.Playlist; +import app.fedilab.android.client.Entities.PushSubscription; import app.fedilab.android.client.Entities.Relationship; import app.fedilab.android.client.Entities.Report; import app.fedilab.android.client.Entities.Results; @@ -56,6 +57,7 @@ public class APIResponse { private List notifications = null; private List relationships = null; private List announcements = null; + private PushSubscription pushSubscription; private String targetedId = null; private Results results = null; private List howToVideos = null; @@ -85,6 +87,14 @@ public class APIResponse { return accounts; } + public PushSubscription getPushSubscription() { + return pushSubscription; + } + + public void setPushSubscription(PushSubscription pushSubscription) { + this.pushSubscription = pushSubscription; + } + public void setAccounts(List accounts) { this.accounts = accounts; } diff --git a/app/src/main/java/app/fedilab/android/client/Entities/PushSubscription.java b/app/src/main/java/app/fedilab/android/client/Entities/PushSubscription.java new file mode 100644 index 000000000..2a3505bde --- /dev/null +++ b/app/src/main/java/app/fedilab/android/client/Entities/PushSubscription.java @@ -0,0 +1,57 @@ +package app.fedilab.android.client.Entities; +/* Copyright 2021 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + +import java.util.HashMap; + +public class PushSubscription { + private String id; + private String endpoint; + private HashMap alerts; + private String server_key; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getEndpoint() { + return endpoint; + } + + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + public HashMap getAlertsList() { + return alerts; + } + + public void setAlertsList(HashMap alertsList) { + this.alerts = alertsList; + } + + public String getServer_key() { + return server_key; + } + + public void setServer_key(String server_key) { + this.server_key = server_key; + } + +} diff --git a/app/src/main/java/app/fedilab/android/client/HttpsConnection.java b/app/src/main/java/app/fedilab/android/client/HttpsConnection.java index 6327b8335..0b71fc085 100644 --- a/app/src/main/java/app/fedilab/android/client/HttpsConnection.java +++ b/app/src/main/java/app/fedilab/android/client/HttpsConnection.java @@ -474,6 +474,63 @@ public class HttpsConnection { } + + String postJson(String urlConnection, int timeout, JSONObject jsonObject, String token) throws IOException, NoSuchAlgorithmException, KeyManagementException, HttpsConnectionException { + + URL url = new URL(urlConnection); + byte[] postDataBytes; + postDataBytes = jsonObject.toString().getBytes(StandardCharsets.UTF_8); + if (proxy != null) + httpURLConnection = (HttpURLConnection) url.openConnection(proxy); + else + httpURLConnection = (HttpURLConnection) url.openConnection(); + httpURLConnection.setRequestProperty("User-Agent", USER_AGENT); + httpURLConnection.setConnectTimeout(timeout * 1000); + httpURLConnection.setDoOutput(true); + if (httpURLConnection instanceof HttpsURLConnection) { + ((HttpsURLConnection) httpURLConnection).setSSLSocketFactory(new TLSSocketFactory(this.instance)); + } + httpURLConnection.setRequestProperty("Content-Type", "application/json"); + httpURLConnection.setRequestProperty("Accept", "application/json"); + httpURLConnection.setRequestMethod("POST"); + setToken(token); + httpURLConnection.setRequestProperty("Content-Length", String.valueOf(postDataBytes.length)); + + + httpURLConnection.getOutputStream().write(postDataBytes); + String response; + if (httpURLConnection.getResponseCode() >= 200 && httpURLConnection.getResponseCode() < 400) { + getSinceMaxId(); + response = converToString(httpURLConnection.getInputStream()); + } else { + String error = null; + if (httpURLConnection.getErrorStream() != null) { + InputStream stream = httpURLConnection.getErrorStream(); + if (stream == null) { + stream = httpURLConnection.getInputStream(); + } + try (Scanner scanner = new Scanner(stream)) { + scanner.useDelimiter("\\Z"); + if (scanner.hasNext()) { + error = scanner.next(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + int responseCode = httpURLConnection.getResponseCode(); + try { + httpURLConnection.getInputStream().close(); + } catch (Exception ignored) { + } + throw new HttpsConnectionException(responseCode, error); + } + getSinceMaxId(); + httpURLConnection.getInputStream().close(); + return response; + + } + @SuppressWarnings("SameParameterValue") String postMisskey(String urlConnection, int timeout, JSONObject paramaters, String token) throws IOException, NoSuchAlgorithmException, KeyManagementException, HttpsConnectionException { URL url = new URL(urlConnection); diff --git a/app/src/main/java/app/fedilab/android/drawers/BaseStatusListAdapter.java b/app/src/main/java/app/fedilab/android/drawers/BaseStatusListAdapter.java index f4106d6f3..e7bbe1524 100644 --- a/app/src/main/java/app/fedilab/android/drawers/BaseStatusListAdapter.java +++ b/app/src/main/java/app/fedilab/android/drawers/BaseStatusListAdapter.java @@ -640,10 +640,27 @@ public abstract class BaseStatusListAdapter extends RecyclerView.Adapter. */ +package app.fedilab.android.interfaces; + +import app.fedilab.android.client.APIResponse; + +public interface OnPostSubscription { + void onSubscription(APIResponse apiResponse); +} diff --git a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java new file mode 100644 index 000000000..d8d85fc3e --- /dev/null +++ b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java @@ -0,0 +1,45 @@ +package app.fedilab.android.services; + +import android.content.Context; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.unifiedpush.android.connector.MessagingReceiver; +import org.unifiedpush.android.connector.MessagingReceiverHandler; + +import app.fedilab.android.helper.PushNotifications; + +class handler implements MessagingReceiverHandler { + @Override + public void onNewEndpoint(@Nullable Context context, @NotNull String s) { + PushNotifications push = new PushNotifications(); + push.registerPushNotifications(context, s); + } + + @Override + public void onRegistrationFailed(@Nullable Context context) { + // Toast ? + } + + @Override + public void onRegistrationRefused(@Nullable Context context) { + // Toast ? + } + + @Override + public void onUnregistered(@Nullable Context context) { + // Remove endpoint & ServerKey + } + + @Override + public void onMessage(@Nullable Context context, @NotNull String s) { + PushNotifications push = new PushNotifications(); + push.displayNotification(context, s); + } +} + +class UnifiedPushService extends MessagingReceiver { + public UnifiedPushService() { + super(new handler()); + } +} diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 16f7dc261..d8fa2cf2d 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -130,4 +130,13 @@ #f3f3f3 #606984 + + + #ff0000 + #ffa500 + #ffff00 + #008000 + #0000ff + #4b0082 + #ee82ee diff --git a/build.gradle b/build.gradle index c95742af2..318aa1c47 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.1' + classpath 'com.android.tools.build:gradle:4.1.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -17,6 +17,7 @@ allprojects { repositories { jcenter() google() + maven { url 'https://jitpack.io' } } } From cb9f31787ceffcad3031fcdb77e5f6f91eb30370 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 21 Feb 2021 17:20:05 +0100 Subject: [PATCH 05/35] unifiedpush - first changes --- app/src/main/java/app/fedilab/android/client/API.java | 1 - .../app/fedilab/android/client/Entities/PushSubscription.java | 1 - app/src/main/java/app/fedilab/android/helper/ECDH.java | 1 + .../main/java/app/fedilab/android/helper/PushNotifications.java | 1 - 4 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/client/API.java b/app/src/main/java/app/fedilab/android/client/API.java index 47e8f77dd..3e5d11ff0 100644 --- a/app/src/main/java/app/fedilab/android/client/API.java +++ b/app/src/main/java/app/fedilab/android/client/API.java @@ -5780,7 +5780,6 @@ public class API { apiResponse.setLists(lists); return apiResponse; } - /** * Subscribe to push notifications * diff --git a/app/src/main/java/app/fedilab/android/client/Entities/PushSubscription.java b/app/src/main/java/app/fedilab/android/client/Entities/PushSubscription.java index 2a3505bde..d5f572fb1 100644 --- a/app/src/main/java/app/fedilab/android/client/Entities/PushSubscription.java +++ b/app/src/main/java/app/fedilab/android/client/Entities/PushSubscription.java @@ -13,7 +13,6 @@ package app.fedilab.android.client.Entities; * * You should have received a copy of the GNU General Public License along with Fedilab; if not, * see . */ - import java.util.HashMap; public class PushSubscription { diff --git a/app/src/main/java/app/fedilab/android/helper/ECDH.java b/app/src/main/java/app/fedilab/android/helper/ECDH.java index a55a8a733..38587814a 100644 --- a/app/src/main/java/app/fedilab/android/helper/ECDH.java +++ b/app/src/main/java/app/fedilab/android/helper/ECDH.java @@ -8,6 +8,7 @@ import android.util.Log; import androidx.preference.PreferenceManager; + import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; diff --git a/app/src/main/java/app/fedilab/android/helper/PushNotifications.java b/app/src/main/java/app/fedilab/android/helper/PushNotifications.java index df0401a04..fac7442f1 100644 --- a/app/src/main/java/app/fedilab/android/helper/PushNotifications.java +++ b/app/src/main/java/app/fedilab/android/helper/PushNotifications.java @@ -2,7 +2,6 @@ package app.fedilab.android.helper; import android.content.Context; import android.util.Base64; - import java.util.Random; public class PushNotifications { From 17d4325303bda19ce16461bbda3b8892dd193617 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 22 Feb 2021 16:56:04 +0100 Subject: [PATCH 06/35] unifiedpush - first changes --- .../android/activities/BaseMainActivity.java | 23 +++++++++++++++---- .../java/app/fedilab/android/client/API.java | 3 --- .../fedilab/android/helper/BaseHelper.java | 1 + .../java/app/fedilab/android/helper/ECDH.java | 4 +--- .../android/services/UnifiedPushService.java | 8 +++++++ 5 files changed, 29 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java index 99f48562f..d4a27135e 100644 --- a/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java @@ -35,7 +35,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.Parcelable; import android.preference.PreferenceManager; -import android.util.Log; import android.util.Patterns; import android.view.LayoutInflater; import android.view.Menu; @@ -77,6 +76,7 @@ import com.google.android.material.navigation.NavigationView; import com.google.android.material.tabs.TabLayout; import org.jetbrains.annotations.NotNull; +import org.unifiedpush.android.connector.Registration; import java.io.BufferedReader; import java.io.File; @@ -114,7 +114,6 @@ import app.fedilab.android.client.Entities.Error; import app.fedilab.android.client.Entities.Filters; import app.fedilab.android.client.Entities.Instance; import app.fedilab.android.client.Entities.ManageTimelines; -import app.fedilab.android.client.Entities.PushSubscription; import app.fedilab.android.client.Entities.Relationship; import app.fedilab.android.client.Entities.Results; import app.fedilab.android.client.Entities.Status; @@ -424,9 +423,14 @@ public abstract class BaseMainActivity extends BaseActivity main_app_container = findViewById(R.id.main_app_container); - Log.v(Helper.TAG, "social: " + social); if (social == UpdateAccountInfoAsyncTask.SOCIAL.MASTODON || social == UpdateAccountInfoAsyncTask.SOCIAL.PLEROMA) { + String serverKey = sharedpreferences.getString(Helper.SERVER_KEY + userId + instance, null); + if (serverKey == null) { + new PostSubscriptionAsyncTask(BaseMainActivity.this, this); + } new PostSubscriptionAsyncTask(BaseMainActivity.this, this); + // Registration registration = new Registration(); + new Registration().registerAppWithDialog(BaseMainActivity.this); } if (social == UpdateAccountInfoAsyncTask.SOCIAL.MASTODON || social == UpdateAccountInfoAsyncTask.SOCIAL.PLEROMA || social == UpdateAccountInfoAsyncTask.SOCIAL.GNU || social == UpdateAccountInfoAsyncTask.SOCIAL.FRIENDICA) { new SyncTimelinesAsyncTask(BaseMainActivity.this, 0, Helper.canFetchList(BaseMainActivity.this, account), BaseMainActivity.this); @@ -1703,6 +1707,10 @@ public abstract class BaseMainActivity extends BaseActivity @Override public void onDestroy() { super.onDestroy(); + if (social == UpdateAccountInfoAsyncTask.SOCIAL.MASTODON || social == UpdateAccountInfoAsyncTask.SOCIAL.PLEROMA) { + new Registration().unregisterApp(BaseMainActivity.this); + } + SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); boolean clearCacheExit = sharedpreferences.getBoolean(Helper.SET_CLEAR_CACHE_EXIT, false); WeakReference contextReference = new WeakReference<>(BaseMainActivity.this); @@ -2434,7 +2442,14 @@ public abstract class BaseMainActivity extends BaseActivity @Override public void onSubscription(APIResponse apiResponse) { - + if (apiResponse != null && apiResponse.getPushSubscription() != null) { + final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); + String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); + String instance = sharedpreferences.getString(Helper.PREF_INSTANCE, Helper.getLiveInstance(BaseMainActivity.this)); + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.SERVER_KEY + userId + instance, apiResponse.getPushSubscription().getServer_key()); + editor.apply(); + } } public enum iconLauncher { diff --git a/app/src/main/java/app/fedilab/android/client/API.java b/app/src/main/java/app/fedilab/android/client/API.java index 3e5d11ff0..5a3b80263 100644 --- a/app/src/main/java/app/fedilab/android/client/API.java +++ b/app/src/main/java/app/fedilab/android/client/API.java @@ -23,7 +23,6 @@ import android.os.Bundle; import android.text.Html; import android.text.SpannableString; import android.util.Base64; -import android.util.Log; import androidx.localbroadcastmanager.content.LocalBroadcastManager; @@ -5811,10 +5810,8 @@ public class API { String auth = Base64.encodeToString(randBytes, Base64.DEFAULT); params.put("subscription[keys][p256dh]", pubKey); params.put("subscription[keys][auth]", auth); - Log.v(Helper.TAG, "params: " + params); try { String response = new HttpsConnection(context, this.instance).post(getAbsoluteUrl("/push/subscription"), 10, params, prefKeyOauthTokenT); - Log.v(Helper.TAG, "response: " + response); pushSubscription = parsePushNotifications(new JSONObject(response)); } catch (HttpsConnection.HttpsConnectionException e) { setError(e.getStatusCode(), e); diff --git a/app/src/main/java/app/fedilab/android/helper/BaseHelper.java b/app/src/main/java/app/fedilab/android/helper/BaseHelper.java index 36321d206..cdcdbe2da 100644 --- a/app/src/main/java/app/fedilab/android/helper/BaseHelper.java +++ b/app/src/main/java/app/fedilab/android/helper/BaseHelper.java @@ -277,6 +277,7 @@ public class BaseHelper { //Some definitions public static final String CLIENT_NAME = "client_name"; public static final String APP_PREFS = "app_prefs"; + public static final String SERVER_KEY = "server_key"; public static final String ID = "id"; public static final String CLIENT_ID = "client_id"; public static final String CLIENT_SECRET = "client_secret"; diff --git a/app/src/main/java/app/fedilab/android/helper/ECDH.java b/app/src/main/java/app/fedilab/android/helper/ECDH.java index 38587814a..513064410 100644 --- a/app/src/main/java/app/fedilab/android/helper/ECDH.java +++ b/app/src/main/java/app/fedilab/android/helper/ECDH.java @@ -140,7 +140,7 @@ public class ECDH { String strPub = prefs.getString(kp_public, ""); String strPriv = prefs.getString(kp_private, ""); - if (strPub.isEmpty() || strPriv.isEmpty() || 1 == 1) { + if (strPub.isEmpty() || strPriv.isEmpty()) { return newPair(context); } try { @@ -152,8 +152,6 @@ public class ECDH { } public String getPublicKey(Context context) { - Log.v(Helper.TAG, "getPair(context): " + getPair(context)); - Log.v(Helper.TAG, "getPair(context).getPublic(): " + getPair(context).getPublic()); return base64Encode(getPair(context).getPublic().getEncoded()); } diff --git a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java index d8d85fc3e..75be23b8b 100644 --- a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java +++ b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java @@ -1,33 +1,40 @@ package app.fedilab.android.services; import android.content.Context; +import android.util.Log; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.unifiedpush.android.connector.MessagingReceiver; import org.unifiedpush.android.connector.MessagingReceiverHandler; +import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.PushNotifications; class handler implements MessagingReceiverHandler { @Override public void onNewEndpoint(@Nullable Context context, @NotNull String s) { + Log.v(Helper.TAG, "onNewEndpoint: " + s); PushNotifications push = new PushNotifications(); push.registerPushNotifications(context, s); + } @Override public void onRegistrationFailed(@Nullable Context context) { + Log.v(Helper.TAG, "onRegistrationFailed: "); // Toast ? } @Override public void onRegistrationRefused(@Nullable Context context) { // Toast ? + Log.v(Helper.TAG, "onRegistrationRefused: "); } @Override public void onUnregistered(@Nullable Context context) { + Log.v(Helper.TAG, "onUnregistered: "); // Remove endpoint & ServerKey } @@ -35,6 +42,7 @@ class handler implements MessagingReceiverHandler { public void onMessage(@Nullable Context context, @NotNull String s) { PushNotifications push = new PushNotifications(); push.displayNotification(context, s); + Log.v(Helper.TAG, "onMessage: " + s); } } From 3ee5d00812c980e991a3a5ceccc77448280c662e Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 23 Feb 2021 18:34:58 +0100 Subject: [PATCH 07/35] some tests --- app/build.gradle | 1 + app/src/main/AndroidManifest.xml | 2 + .../android/activities/BaseMainActivity.java | 43 +++++------------- .../asynctasks/PostSubscriptionAsyncTask.java | 10 +++-- .../java/app/fedilab/android/client/API.java | 5 ++- .../fedilab/android/client/PeertubeAPI.java | 4 +- .../fedilab/android/helper/BaseHelper.java | 1 + .../android/helper/PushNotifications.java | 15 +++++++ .../android/services/UnifiedPushService.java | 45 +++++++++++++++++-- .../fedilab/android/sqlite/AccountDAO.java | 16 +++++++ 10 files changed, 100 insertions(+), 42 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 686d93680..997a85f7a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -177,6 +177,7 @@ dependencies { implementation "com.madgag.spongycastle:prov:1.58.0.0" implementation "com.madgag.spongycastle:bcpkix-jdk15on:1.58.0.0" implementation "com.madgag.spongycastle:bcpg-jdk15on:1.58.0.0" + implementation 'com.github.UnifiedPush:android-connector:1.0.0' //Flavors diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 31711a6bf..1ae852253 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -460,6 +460,8 @@ android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> + + contextReference = new WeakReference<>(BaseMainActivity.this); @@ -2440,17 +2430,6 @@ public abstract class BaseMainActivity extends BaseActivity protected abstract void launchOwnerNotificationsActivity(); - @Override - public void onSubscription(APIResponse apiResponse) { - if (apiResponse != null && apiResponse.getPushSubscription() != null) { - final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); - String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); - String instance = sharedpreferences.getString(Helper.PREF_INSTANCE, Helper.getLiveInstance(BaseMainActivity.this)); - SharedPreferences.Editor editor = sharedpreferences.edit(); - editor.putString(Helper.SERVER_KEY + userId + instance, apiResponse.getPushSubscription().getServer_key()); - editor.apply(); - } - } public enum iconLauncher { BUBBLES, diff --git a/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java b/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java index aca5ff329..ec1521bc2 100644 --- a/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java +++ b/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java @@ -22,6 +22,7 @@ import java.lang.ref.WeakReference; import app.fedilab.android.client.API; import app.fedilab.android.client.APIResponse; +import app.fedilab.android.client.Entities.Account; import app.fedilab.android.interfaces.OnPostSubscription; @@ -30,18 +31,21 @@ public class PostSubscriptionAsyncTask { private final OnPostSubscription listener; private final WeakReference contextReference; private APIResponse apiResponse; + private final String endpoint; + private final Account account; - - public PostSubscriptionAsyncTask(Context context, OnPostSubscription onPostSubscription) { + public PostSubscriptionAsyncTask(Context context, Account account, String endpoint, OnPostSubscription onPostSubscription) { this.contextReference = new WeakReference<>(context); this.listener = onPostSubscription; + this.endpoint = endpoint; + this.account = account; doInBackground(); } protected void doInBackground() { new Thread(() -> { - apiResponse = new API(contextReference.get()).pushSubscription(); + apiResponse = new API(contextReference.get(), account.getInstance(), account.getToken()).pushSubscription(account, endpoint); Handler mainHandler = new Handler(Looper.getMainLooper()); Runnable myRunnable = () -> listener.onSubscription(apiResponse); mainHandler.post(myRunnable); diff --git a/app/src/main/java/app/fedilab/android/client/API.java b/app/src/main/java/app/fedilab/android/client/API.java index 5a3b80263..d300a22ee 100644 --- a/app/src/main/java/app/fedilab/android/client/API.java +++ b/app/src/main/java/app/fedilab/android/client/API.java @@ -5779,12 +5779,13 @@ public class API { apiResponse.setLists(lists); return apiResponse; } + /** * Subscribe to push notifications * * @return APIResponse */ - public APIResponse pushSubscription() { + public APIResponse pushSubscription(Account account, String endpoint) { PushSubscription pushSubscription = new PushSubscription(); final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true); @@ -5801,7 +5802,7 @@ public class API { params.put("data[alerts][reblog]", String.valueOf(notif_share)); params.put("data[alerts][poll]", String.valueOf(notif_poll)); - params.put("subscription[endpoint]", getAbsoluteUrl("/streaming/user")); + params.put("subscription[endpoint]", endpoint); ECDH ecdh = ECDH.getInstance(); String pubKey = ecdh.getPublicKey(context); diff --git a/app/src/main/java/app/fedilab/android/client/PeertubeAPI.java b/app/src/main/java/app/fedilab/android/client/PeertubeAPI.java index 3f8c3572a..b1f60ae2b 100644 --- a/app/src/main/java/app/fedilab/android/client/PeertubeAPI.java +++ b/app/src/main/java/app/fedilab/android/client/PeertubeAPI.java @@ -1638,8 +1638,8 @@ public class PeertubeAPI { String errorM = jsonObject.get("error").toString(); message = "Error " + statusCode + " : " + errorM; } catch (JSONException e) { - if (error.getMessage().split(".").length > 0) { - String errorM = error.getMessage().split(".")[0]; + if (error.getMessage().split("\\.").length > 0) { + String errorM = error.getMessage().split("\\.")[0]; message = "Error " + statusCode + " : " + errorM; } } diff --git a/app/src/main/java/app/fedilab/android/helper/BaseHelper.java b/app/src/main/java/app/fedilab/android/helper/BaseHelper.java index cdcdbe2da..d1ec08ed5 100644 --- a/app/src/main/java/app/fedilab/android/helper/BaseHelper.java +++ b/app/src/main/java/app/fedilab/android/helper/BaseHelper.java @@ -278,6 +278,7 @@ public class BaseHelper { public static final String CLIENT_NAME = "client_name"; public static final String APP_PREFS = "app_prefs"; public static final String SERVER_KEY = "server_key"; + public static final String SERVER_ENDPOINT = "server_endpoint"; public static final String ID = "id"; public static final String CLIENT_ID = "client_id"; public static final String CLIENT_SECRET = "client_secret"; diff --git a/app/src/main/java/app/fedilab/android/helper/PushNotifications.java b/app/src/main/java/app/fedilab/android/helper/PushNotifications.java index fac7442f1..2d6f9e5d0 100644 --- a/app/src/main/java/app/fedilab/android/helper/PushNotifications.java +++ b/app/src/main/java/app/fedilab/android/helper/PushNotifications.java @@ -2,8 +2,14 @@ package app.fedilab.android.helper; import android.content.Context; import android.util.Base64; + +import org.unifiedpush.android.connector.Registration; + +import java.util.List; import java.util.Random; +import app.fedilab.android.activities.BaseMainActivity; + public class PushNotifications { public void registerPushNotifications(Context context, String endpoint) { ECDH ecdh = new ECDH(); @@ -19,6 +25,15 @@ public class PushNotifications { ecdh.saveServerKey(context, server_key); } + public static void getDistributors(Context context) { + List distributors = new Registration().getDistributors(context); + if (distributors.isEmpty()) { + + } else { + + } + } + public void displayNotification(Context context, String ciphered) { ECDH ecdh = new ECDH(); byte[] secret = ecdh.getSecret(context); diff --git a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java index 75be23b8b..c6c935efd 100644 --- a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java +++ b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java @@ -1,6 +1,8 @@ package app.fedilab.android.services; import android.content.Context; +import android.content.SharedPreferences; +import android.database.sqlite.SQLiteDatabase; import android.util.Log; import org.jetbrains.annotations.NotNull; @@ -8,16 +10,41 @@ import org.jetbrains.annotations.Nullable; import org.unifiedpush.android.connector.MessagingReceiver; import org.unifiedpush.android.connector.MessagingReceiverHandler; +import java.util.List; + +import app.fedilab.android.activities.BaseMainActivity; +import app.fedilab.android.activities.LiveNotificationSettingsAccountsActivity; +import app.fedilab.android.asynctasks.PostSubscriptionAsyncTask; +import app.fedilab.android.client.APIResponse; +import app.fedilab.android.client.Entities.Account; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.PushNotifications; +import app.fedilab.android.interfaces.OnPostSubscription; +import app.fedilab.android.sqlite.AccountDAO; +import app.fedilab.android.sqlite.Sqlite; + +import static android.content.Context.MODE_PRIVATE; + +class handler implements MessagingReceiverHandler, OnPostSubscription { + + private Context context; -class handler implements MessagingReceiverHandler { @Override public void onNewEndpoint(@Nullable Context context, @NotNull String s) { Log.v(Helper.TAG, "onNewEndpoint: " + s); + final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.SERVER_ENDPOINT, s); + editor.apply(); + PushNotifications push = new PushNotifications(); push.registerPushNotifications(context, s); - + this.context = context; + SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + List accountPush = new AccountDAO(context, db).getPushNotificationAccounts(); + for (Account account : accountPush) { + new PostSubscriptionAsyncTask(context, account, s, this); + } } @Override @@ -44,9 +71,21 @@ class handler implements MessagingReceiverHandler { push.displayNotification(context, s); Log.v(Helper.TAG, "onMessage: " + s); } + + @Override + public void onSubscription(APIResponse apiResponse) { + if (apiResponse != null && apiResponse.getPushSubscription() != null) { + final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); + String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); + String instance = sharedpreferences.getString(Helper.PREF_INSTANCE, Helper.getLiveInstance(context)); + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.SERVER_KEY + userId + instance, apiResponse.getPushSubscription().getServer_key()); + editor.apply(); + } + } } -class UnifiedPushService extends MessagingReceiver { +public class UnifiedPushService extends MessagingReceiver { public UnifiedPushService() { super(new handler()); } diff --git a/app/src/main/java/app/fedilab/android/sqlite/AccountDAO.java b/app/src/main/java/app/fedilab/android/sqlite/AccountDAO.java index cd963c9cc..642057991 100644 --- a/app/src/main/java/app/fedilab/android/sqlite/AccountDAO.java +++ b/app/src/main/java/app/fedilab/android/sqlite/AccountDAO.java @@ -278,6 +278,22 @@ public class AccountDAO { } + /** + * Returns all Account in db + * + * @return Account List + */ + public List getPushNotificationAccounts() { + + try { + Cursor c = db.query(Sqlite.TABLE_USER_ACCOUNT, null, "(" + Sqlite.COL_SOCIAL + " = 'MASTODON' OR " + Sqlite.COL_SOCIAL + " = 'PLEROMA') AND " + Sqlite.COL_OAUTHTOKEN + " != 'null'", null, null, null, Sqlite.COL_INSTANCE + " ASC", null); + return cursorToListUser(c); + } catch (Exception e) { + return null; + } + } + + /** * Returns an Account by token * From 9d40901631912518f3af0eea23c057c0b02851f8 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 23 Feb 2021 18:40:57 +0100 Subject: [PATCH 08/35] Fix issue with sharedpref --- .../android/asynctasks/PostSubscriptionAsyncTask.java | 5 +++++ .../app/fedilab/android/services/UnifiedPushService.java | 6 ++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java b/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java index ec1521bc2..900b4f96d 100644 --- a/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java +++ b/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java @@ -19,6 +19,8 @@ import android.os.Handler; import android.os.Looper; import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; import app.fedilab.android.client.API; import app.fedilab.android.client.APIResponse; @@ -46,6 +48,9 @@ public class PostSubscriptionAsyncTask { protected void doInBackground() { new Thread(() -> { apiResponse = new API(contextReference.get(), account.getInstance(), account.getToken()).pushSubscription(account, endpoint); + List accountList = new ArrayList<>(); + accountList.add(account); + apiResponse.setAccounts(accountList); Handler mainHandler = new Handler(Looper.getMainLooper()); Runnable myRunnable = () -> listener.onSubscription(apiResponse); mainHandler.post(myRunnable); diff --git a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java index c6c935efd..a1ab638d5 100644 --- a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java +++ b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java @@ -74,12 +74,10 @@ class handler implements MessagingReceiverHandler, OnPostSubscription { @Override public void onSubscription(APIResponse apiResponse) { - if (apiResponse != null && apiResponse.getPushSubscription() != null) { + if (apiResponse != null && apiResponse.getPushSubscription() != null && apiResponse.getAccounts() != null) { final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); - String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); - String instance = sharedpreferences.getString(Helper.PREF_INSTANCE, Helper.getLiveInstance(context)); SharedPreferences.Editor editor = sharedpreferences.edit(); - editor.putString(Helper.SERVER_KEY + userId + instance, apiResponse.getPushSubscription().getServer_key()); + editor.putString(Helper.SERVER_KEY + apiResponse.getAccounts().get(0).getId() + apiResponse.getAccounts().get(0).getInstance(), apiResponse.getPushSubscription().getServer_key()); editor.apply(); } } From 7ed93980090bf68ebc769435f358e79f45cf7e47 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 23 Feb 2021 19:08:45 +0100 Subject: [PATCH 09/35] Fix issue with sharedpref --- .../asynctasks/PostSubscriptionAsyncTask.java | 2 +- .../java/app/fedilab/android/client/API.java | 8 ++++--- .../java/app/fedilab/android/helper/ECDH.java | 24 ++++++++++--------- .../android/helper/PushNotifications.java | 11 ++++----- .../android/services/UnifiedPushService.java | 11 +++++---- 5 files changed, 31 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java b/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java index 900b4f96d..9d6461842 100644 --- a/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java +++ b/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java @@ -47,7 +47,7 @@ public class PostSubscriptionAsyncTask { protected void doInBackground() { new Thread(() -> { - apiResponse = new API(contextReference.get(), account.getInstance(), account.getToken()).pushSubscription(account, endpoint); + apiResponse = new API(contextReference.get(), account.getInstance(), account.getToken()).pushSubscription(endpoint, account); List accountList = new ArrayList<>(); accountList.add(account); apiResponse.setAccounts(accountList); diff --git a/app/src/main/java/app/fedilab/android/client/API.java b/app/src/main/java/app/fedilab/android/client/API.java index d300a22ee..2bdcde59d 100644 --- a/app/src/main/java/app/fedilab/android/client/API.java +++ b/app/src/main/java/app/fedilab/android/client/API.java @@ -23,6 +23,7 @@ import android.os.Bundle; import android.text.Html; import android.text.SpannableString; import android.util.Base64; +import android.util.Log; import androidx.localbroadcastmanager.content.LocalBroadcastManager; @@ -5785,7 +5786,7 @@ public class API { * * @return APIResponse */ - public APIResponse pushSubscription(Account account, String endpoint) { + public APIResponse pushSubscription(String endpoint, Account account) { PushSubscription pushSubscription = new PushSubscription(); final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true); @@ -5805,15 +5806,16 @@ public class API { params.put("subscription[endpoint]", endpoint); ECDH ecdh = ECDH.getInstance(); - String pubKey = ecdh.getPublicKey(context); + String pubKey = ecdh.getPublicKey(context, account); byte[] randBytes = new byte[16]; new Random().nextBytes(randBytes); String auth = Base64.encodeToString(randBytes, Base64.DEFAULT); params.put("subscription[keys][p256dh]", pubKey); params.put("subscription[keys][auth]", auth); try { - String response = new HttpsConnection(context, this.instance).post(getAbsoluteUrl("/push/subscription"), 10, params, prefKeyOauthTokenT); + String response = new HttpsConnection(context, this.instance).post(getAbsoluteUrl("/push/subscription"), 10, params, account.getToken()); pushSubscription = parsePushNotifications(new JSONObject(response)); + Log.v(Helper.TAG, "response: " + response); } catch (HttpsConnection.HttpsConnectionException e) { setError(e.getStatusCode(), e); e.printStackTrace(); diff --git a/app/src/main/java/app/fedilab/android/helper/ECDH.java b/app/src/main/java/app/fedilab/android/helper/ECDH.java index 513064410..c0ac1cfdf 100644 --- a/app/src/main/java/app/fedilab/android/helper/ECDH.java +++ b/app/src/main/java/app/fedilab/android/helper/ECDH.java @@ -24,6 +24,8 @@ import java.security.spec.X509EncodedKeySpec; import javax.crypto.KeyAgreement; +import app.fedilab.android.client.Entities.Account; + // https://github.com/nelenkov/ecdh-kx/blob/master/src/org/nick/ecdhkx/Crypto.java public class ECDH { @@ -114,7 +116,7 @@ public class ECDH { return new KeyPair(readPublicKey(pubKeyStr), readPrivateKey(privKeyStr)); } - KeyPair newPair(Context context) { + KeyPair newPair(Context context, Account account) { SharedPreferences.Editor prefsEditor = PreferenceManager .getDefaultSharedPreferences(context).edit(); @@ -127,21 +129,21 @@ public class ECDH { return null; } - prefsEditor.putString(kp_public, base64Encode(kp.getPublic().getEncoded())); - prefsEditor.putString(kp_private, base64Encode(kp.getPrivate().getEncoded())); + prefsEditor.putString(kp_public + account.getId() + account.getInstance(), base64Encode(kp.getPublic().getEncoded())); + prefsEditor.putString(kp_private + account.getId() + account.getInstance(), base64Encode(kp.getPrivate().getEncoded())); prefsEditor.commit(); return kp; } - synchronized KeyPair getPair(Context context) { + synchronized KeyPair getPair(Context context, Account account) { SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(context); - String strPub = prefs.getString(kp_public, ""); - String strPriv = prefs.getString(kp_private, ""); + String strPub = prefs.getString(kp_public + account.getId() + account.getInstance(), ""); + String strPriv = prefs.getString(kp_private + account.getId() + account.getInstance(), ""); if (strPub.isEmpty() || strPriv.isEmpty()) { - return newPair(context); + return newPair(context, account); } try { return readKeyPair(strPub, strPriv); @@ -151,8 +153,8 @@ public class ECDH { return null; } - public String getPublicKey(Context context) { - return base64Encode(getPair(context).getPublic().getEncoded()); + public String getPublicKey(Context context, Account account) { + return base64Encode(getPair(context, account).getPublic().getEncoded()); } void saveServerKey(Context context, String strPeerPublic) { @@ -170,9 +172,9 @@ public class ECDH { ); } - byte[] getSecret(Context context) { + byte[] getSecret(Context context, Account account) { try { - return generateSecret(getPair(context).getPrivate(), getServerKey(context)); + return generateSecret(getPair(context, account).getPrivate(), getServerKey(context)); } catch (Exception e) { e.printStackTrace(); return null; diff --git a/app/src/main/java/app/fedilab/android/helper/PushNotifications.java b/app/src/main/java/app/fedilab/android/helper/PushNotifications.java index 2d6f9e5d0..affc7d364 100644 --- a/app/src/main/java/app/fedilab/android/helper/PushNotifications.java +++ b/app/src/main/java/app/fedilab/android/helper/PushNotifications.java @@ -9,19 +9,18 @@ import java.util.List; import java.util.Random; import app.fedilab.android.activities.BaseMainActivity; +import app.fedilab.android.client.Entities.Account; public class PushNotifications { - public void registerPushNotifications(Context context, String endpoint) { + public void registerPushNotifications(Context context, Account account, String endpoint, String server_key) { ECDH ecdh = new ECDH(); - String pubKey = ecdh.getPublicKey(context); + String pubKey = ecdh.getPublicKey(context, account); byte[] randBytes = new byte[16]; new Random().nextBytes(randBytes); String auth = Base64.encodeToString(randBytes, Base64.DEFAULT); //register - String server_key = ""; //fetch in the json - ecdh.saveServerKey(context, server_key); } @@ -34,9 +33,9 @@ public class PushNotifications { } } - public void displayNotification(Context context, String ciphered) { + public void displayNotification(Context context, Account account, String ciphered) { ECDH ecdh = new ECDH(); - byte[] secret = ecdh.getSecret(context); + byte[] secret = ecdh.getSecret(context, account); //process with the event // https://openacs.org/webpush-demo/report.html // decrypt using AES 128 GCM diff --git a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java index a1ab638d5..2325739d8 100644 --- a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java +++ b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java @@ -28,6 +28,7 @@ import static android.content.Context.MODE_PRIVATE; class handler implements MessagingReceiverHandler, OnPostSubscription { private Context context; + private String endpoint; @Override public void onNewEndpoint(@Nullable Context context, @NotNull String s) { @@ -36,9 +37,8 @@ class handler implements MessagingReceiverHandler, OnPostSubscription { SharedPreferences.Editor editor = sharedpreferences.edit(); editor.putString(Helper.SERVER_ENDPOINT, s); editor.apply(); + endpoint = s; - PushNotifications push = new PushNotifications(); - push.registerPushNotifications(context, s); this.context = context; SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); List accountPush = new AccountDAO(context, db).getPushNotificationAccounts(); @@ -68,8 +68,8 @@ class handler implements MessagingReceiverHandler, OnPostSubscription { @Override public void onMessage(@Nullable Context context, @NotNull String s) { PushNotifications push = new PushNotifications(); - push.displayNotification(context, s); - Log.v(Helper.TAG, "onMessage: " + s); + Log.v(Helper.TAG, "Message: " + s); + push.displayNotification(context, null, s); } @Override @@ -77,8 +77,11 @@ class handler implements MessagingReceiverHandler, OnPostSubscription { if (apiResponse != null && apiResponse.getPushSubscription() != null && apiResponse.getAccounts() != null) { final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); SharedPreferences.Editor editor = sharedpreferences.edit(); + Log.v(Helper.TAG, "OK: " + apiResponse.getAccounts().get(0).getId() + apiResponse.getAccounts().get(0).getInstance() + " --- " + apiResponse.getPushSubscription().getServer_key()); editor.putString(Helper.SERVER_KEY + apiResponse.getAccounts().get(0).getId() + apiResponse.getAccounts().get(0).getInstance(), apiResponse.getPushSubscription().getServer_key()); editor.apply(); + PushNotifications push = new PushNotifications(); + push.registerPushNotifications(context, apiResponse.getAccounts().get(0), endpoint, apiResponse.getPushSubscription().getServer_key()); } } } From f0affaa33294110dbe7c02e1c4425f66346a3967 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 23 Feb 2021 19:15:10 +0100 Subject: [PATCH 10/35] multi-account --- app/src/main/java/app/fedilab/android/helper/ECDH.java | 10 +++++----- .../app/fedilab/android/helper/PushNotifications.java | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/helper/ECDH.java b/app/src/main/java/app/fedilab/android/helper/ECDH.java index c0ac1cfdf..3f00ad8fe 100644 --- a/app/src/main/java/app/fedilab/android/helper/ECDH.java +++ b/app/src/main/java/app/fedilab/android/helper/ECDH.java @@ -157,24 +157,24 @@ public class ECDH { return base64Encode(getPair(context, account).getPublic().getEncoded()); } - void saveServerKey(Context context, String strPeerPublic) { + void saveServerKey(Context context, Account account, String strPeerPublic) { SharedPreferences.Editor prefsEditor = PreferenceManager .getDefaultSharedPreferences(context).edit(); - prefsEditor.putString(peer_public, strPeerPublic); + prefsEditor.putString(peer_public + account.getId() + account.getInstance(), strPeerPublic); prefsEditor.commit(); } - PublicKey getServerKey(Context context) throws Exception { + PublicKey getServerKey(Context context, Account account) throws Exception { return readPublicKey( PreferenceManager.getDefaultSharedPreferences(context) - .getString(peer_public, "") + .getString(peer_public + account.getId() + account.getInstance(), "") ); } byte[] getSecret(Context context, Account account) { try { - return generateSecret(getPair(context, account).getPrivate(), getServerKey(context)); + return generateSecret(getPair(context, account).getPrivate(), getServerKey(context, account)); } catch (Exception e) { e.printStackTrace(); return null; diff --git a/app/src/main/java/app/fedilab/android/helper/PushNotifications.java b/app/src/main/java/app/fedilab/android/helper/PushNotifications.java index affc7d364..85577e508 100644 --- a/app/src/main/java/app/fedilab/android/helper/PushNotifications.java +++ b/app/src/main/java/app/fedilab/android/helper/PushNotifications.java @@ -21,7 +21,7 @@ public class PushNotifications { //register - ecdh.saveServerKey(context, server_key); + ecdh.saveServerKey(context, account, server_key); } public static void getDistributors(Context context) { From 28058fd5df3dc9b93ae2c4f42cf891c45a65252f Mon Sep 17 00:00:00 2001 From: p1gp1g Date: Wed, 24 Feb 2021 00:52:44 +0100 Subject: [PATCH 11/35] UP: move code to helper + single private key --- app/build.gradle | 2 +- .../java/app/fedilab/android/client/API.java | 3 +- .../java/app/fedilab/android/helper/ECDH.java | 22 +++++------ .../android/helper/PushNotifications.java | 33 +++++++++++----- .../android/services/UnifiedPushService.java | 38 +++++-------------- 5 files changed, 47 insertions(+), 51 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 997a85f7a..59d563152 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -38,7 +38,7 @@ android { } productFlavors { fdroid { - applicationId "fr.gouv.etalab.mastodon" + applicationId "fr.gouv.etalab.mastodon.updev" buildConfigField "boolean", "DONATIONS", "true" buildConfigField "boolean", "lite", "false" resValue "string", "app_name", "Fedilab" diff --git a/app/src/main/java/app/fedilab/android/client/API.java b/app/src/main/java/app/fedilab/android/client/API.java index 2bdcde59d..0b872ffc7 100644 --- a/app/src/main/java/app/fedilab/android/client/API.java +++ b/app/src/main/java/app/fedilab/android/client/API.java @@ -5806,7 +5806,7 @@ public class API { params.put("subscription[endpoint]", endpoint); ECDH ecdh = ECDH.getInstance(); - String pubKey = ecdh.getPublicKey(context, account); + String pubKey = ecdh.getPublicKey(context); byte[] randBytes = new byte[16]; new Random().nextBytes(randBytes); String auth = Base64.encodeToString(randBytes, Base64.DEFAULT); @@ -5816,6 +5816,7 @@ public class API { String response = new HttpsConnection(context, this.instance).post(getAbsoluteUrl("/push/subscription"), 10, params, account.getToken()); pushSubscription = parsePushNotifications(new JSONObject(response)); Log.v(Helper.TAG, "response: " + response); + ecdh.saveServerKey(context, account, pushSubscription.getServer_key()); } catch (HttpsConnection.HttpsConnectionException e) { setError(e.getStatusCode(), e); e.printStackTrace(); diff --git a/app/src/main/java/app/fedilab/android/helper/ECDH.java b/app/src/main/java/app/fedilab/android/helper/ECDH.java index 3f00ad8fe..784bd3f37 100644 --- a/app/src/main/java/app/fedilab/android/helper/ECDH.java +++ b/app/src/main/java/app/fedilab/android/helper/ECDH.java @@ -116,7 +116,7 @@ public class ECDH { return new KeyPair(readPublicKey(pubKeyStr), readPrivateKey(privKeyStr)); } - KeyPair newPair(Context context, Account account) { + KeyPair newPair(Context context) { SharedPreferences.Editor prefsEditor = PreferenceManager .getDefaultSharedPreferences(context).edit(); @@ -129,21 +129,21 @@ public class ECDH { return null; } - prefsEditor.putString(kp_public + account.getId() + account.getInstance(), base64Encode(kp.getPublic().getEncoded())); - prefsEditor.putString(kp_private + account.getId() + account.getInstance(), base64Encode(kp.getPrivate().getEncoded())); + prefsEditor.putString(kp_public, base64Encode(kp.getPublic().getEncoded())); + prefsEditor.putString(kp_private, base64Encode(kp.getPrivate().getEncoded())); prefsEditor.commit(); return kp; } - synchronized KeyPair getPair(Context context, Account account) { + synchronized KeyPair getPair(Context context) { SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(context); - String strPub = prefs.getString(kp_public + account.getId() + account.getInstance(), ""); - String strPriv = prefs.getString(kp_private + account.getId() + account.getInstance(), ""); + String strPub = prefs.getString(kp_public, ""); + String strPriv = prefs.getString(kp_private, ""); if (strPub.isEmpty() || strPriv.isEmpty()) { - return newPair(context, account); + return newPair(context); } try { return readKeyPair(strPub, strPriv); @@ -153,11 +153,11 @@ public class ECDH { return null; } - public String getPublicKey(Context context, Account account) { - return base64Encode(getPair(context, account).getPublic().getEncoded()); + public String getPublicKey(Context context) { + return base64Encode(getPair(context).getPublic().getEncoded()); } - void saveServerKey(Context context, Account account, String strPeerPublic) { + public void saveServerKey(Context context, Account account, String strPeerPublic) { SharedPreferences.Editor prefsEditor = PreferenceManager .getDefaultSharedPreferences(context).edit(); @@ -174,7 +174,7 @@ public class ECDH { byte[] getSecret(Context context, Account account) { try { - return generateSecret(getPair(context, account).getPrivate(), getServerKey(context, account)); + return generateSecret(getPair(context).getPrivate(), getServerKey(context, account)); } catch (Exception e) { e.printStackTrace(); return null; diff --git a/app/src/main/java/app/fedilab/android/helper/PushNotifications.java b/app/src/main/java/app/fedilab/android/helper/PushNotifications.java index 85577e508..27e7f4ae4 100644 --- a/app/src/main/java/app/fedilab/android/helper/PushNotifications.java +++ b/app/src/main/java/app/fedilab/android/helper/PushNotifications.java @@ -1,7 +1,10 @@ package app.fedilab.android.helper; import android.content.Context; +import android.content.SharedPreferences; +import android.database.sqlite.SQLiteDatabase; import android.util.Base64; +import android.util.Log; import org.unifiedpush.android.connector.Registration; @@ -9,19 +12,26 @@ import java.util.List; import java.util.Random; import app.fedilab.android.activities.BaseMainActivity; +import app.fedilab.android.asynctasks.PostSubscriptionAsyncTask; +import app.fedilab.android.client.APIResponse; import app.fedilab.android.client.Entities.Account; +import app.fedilab.android.interfaces.OnPostSubscription; +import app.fedilab.android.sqlite.AccountDAO; +import app.fedilab.android.sqlite.Sqlite; -public class PushNotifications { - public void registerPushNotifications(Context context, Account account, String endpoint, String server_key) { - ECDH ecdh = new ECDH(); - String pubKey = ecdh.getPublicKey(context, account); - byte[] randBytes = new byte[16]; - new Random().nextBytes(randBytes); - String auth = Base64.encodeToString(randBytes, Base64.DEFAULT); +import static android.content.Context.MODE_PRIVATE; - //register +public class PushNotifications implements OnPostSubscription { - ecdh.saveServerKey(context, account, server_key); + private Context context; + + public void registerPushNotifications(Context context, String endpoint) { + this.context = context; + SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + List accountPush = new AccountDAO(context, db).getPushNotificationAccounts(); + for (Account account : accountPush) { + new PostSubscriptionAsyncTask(context, account, endpoint, this); + } } public static void getDistributors(Context context) { @@ -39,7 +49,10 @@ public class PushNotifications { //process with the event // https://openacs.org/webpush-demo/report.html // decrypt using AES 128 GCM + } - + @Override + public void onSubscription(APIResponse apiResponse) { + //TODO je ne sais pas si c'est toujours utile } } diff --git a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java index 2325739d8..d898ce87b 100644 --- a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java +++ b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java @@ -25,26 +25,21 @@ import app.fedilab.android.sqlite.Sqlite; import static android.content.Context.MODE_PRIVATE; -class handler implements MessagingReceiverHandler, OnPostSubscription { +class handler implements MessagingReceiverHandler { private Context context; private String endpoint; @Override - public void onNewEndpoint(@Nullable Context context, @NotNull String s) { - Log.v(Helper.TAG, "onNewEndpoint: " + s); + public void onNewEndpoint(@Nullable Context context, @NotNull String endpoint) { + Log.v(Helper.TAG, "onNewEndpoint: " + endpoint); final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); SharedPreferences.Editor editor = sharedpreferences.edit(); - editor.putString(Helper.SERVER_ENDPOINT, s); + editor.putString(Helper.SERVER_ENDPOINT, endpoint); editor.apply(); - endpoint = s; - this.context = context; - SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); - List accountPush = new AccountDAO(context, db).getPushNotificationAccounts(); - for (Account account : accountPush) { - new PostSubscriptionAsyncTask(context, account, s, this); - } + new PushNotifications() + .registerPushNotifications(context, endpoint); } @Override @@ -66,23 +61,10 @@ class handler implements MessagingReceiverHandler, OnPostSubscription { } @Override - public void onMessage(@Nullable Context context, @NotNull String s) { - PushNotifications push = new PushNotifications(); - Log.v(Helper.TAG, "Message: " + s); - push.displayNotification(context, null, s); - } - - @Override - public void onSubscription(APIResponse apiResponse) { - if (apiResponse != null && apiResponse.getPushSubscription() != null && apiResponse.getAccounts() != null) { - final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); - SharedPreferences.Editor editor = sharedpreferences.edit(); - Log.v(Helper.TAG, "OK: " + apiResponse.getAccounts().get(0).getId() + apiResponse.getAccounts().get(0).getInstance() + " --- " + apiResponse.getPushSubscription().getServer_key()); - editor.putString(Helper.SERVER_KEY + apiResponse.getAccounts().get(0).getId() + apiResponse.getAccounts().get(0).getInstance(), apiResponse.getPushSubscription().getServer_key()); - editor.apply(); - PushNotifications push = new PushNotifications(); - push.registerPushNotifications(context, apiResponse.getAccounts().get(0), endpoint, apiResponse.getPushSubscription().getServer_key()); - } + public void onMessage(@Nullable Context context, @NotNull String message) { + Log.v(Helper.TAG, "Message: " + message); + new PushNotifications() + .displayNotification(context, null, message); } } From 48312017ef9f66951e8c6297de06478ce02126e3 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 24 Feb 2021 10:33:32 +0100 Subject: [PATCH 12/35] Improve update process --- app/build.gradle | 2 +- .../asynctasks/PostSubscriptionAsyncTask.java | 7 ++- .../java/app/fedilab/android/client/API.java | 60 ++++++++++++++++++- 3 files changed, 66 insertions(+), 3 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 59d563152..997a85f7a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -38,7 +38,7 @@ android { } productFlavors { fdroid { - applicationId "fr.gouv.etalab.mastodon.updev" + applicationId "fr.gouv.etalab.mastodon" buildConfigField "boolean", "DONATIONS", "true" buildConfigField "boolean", "lite", "false" resValue "string", "app_name", "Fedilab" diff --git a/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java b/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java index 9d6461842..1e183cc25 100644 --- a/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java +++ b/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java @@ -47,7 +47,12 @@ public class PostSubscriptionAsyncTask { protected void doInBackground() { new Thread(() -> { - apiResponse = new API(contextReference.get(), account.getInstance(), account.getToken()).pushSubscription(endpoint, account); + apiResponse = new API(contextReference.get(), account.getInstance(), account.getToken()).getPushSubscription(); + if (apiResponse == null || apiResponse.getPushSubscription() == null) { + apiResponse = new API(contextReference.get(), account.getInstance(), account.getToken()).pushSubscription(endpoint, account); + } else if (apiResponse != null && endpoint.compareTo(apiResponse.getPushSubscription().getEndpoint()) != 0) { + apiResponse = new API(contextReference.get(), account.getInstance(), account.getToken()).updatePushSubscription(endpoint); + } List accountList = new ArrayList<>(); accountList.add(account); apiResponse.setAccounts(accountList); diff --git a/app/src/main/java/app/fedilab/android/client/API.java b/app/src/main/java/app/fedilab/android/client/API.java index 0b872ffc7..d99111eb3 100644 --- a/app/src/main/java/app/fedilab/android/client/API.java +++ b/app/src/main/java/app/fedilab/android/client/API.java @@ -5781,6 +5781,64 @@ public class API { return apiResponse; } + /** + * Get subscribed push notifications + * + * @return APIResponse + */ + public APIResponse getPushSubscription() { + PushSubscription pushSubscription = null; + try { + String response = new HttpsConnection(context, this.instance).get(getAbsoluteUrl("/push/subscription"), 10, null, prefKeyOauthTokenT); + pushSubscription = parsePushNotifications(new JSONObject(response)); + Log.v(Helper.TAG, "response2: " + response); + } catch (HttpsConnection.HttpsConnectionException e) { + setError(e.getStatusCode(), e); + e.printStackTrace(); + } catch (NoSuchAlgorithmException | IOException | KeyManagementException | JSONException e) { + e.printStackTrace(); + } + apiResponse.setPushSubscription(pushSubscription); + return apiResponse; + } + + + /** + * Update subscribed push notifications + * + * @return APIResponse + */ + public APIResponse updatePushSubscription(String endpoint) { + PushSubscription pushSubscription = new PushSubscription(); + final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true); + boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true); + boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true); + boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true); + boolean notif_status = sharedpreferences.getBoolean(Helper.SET_NOTIF_STATUS, true); + boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true); + + HashMap params = new HashMap<>(); + params.put("data[alerts][follow]", String.valueOf(notif_follow)); + params.put("data[alerts][mention]", String.valueOf(notif_mention)); + params.put("data[alerts][favourite]", String.valueOf(notif_add)); + params.put("data[alerts][reblog]", String.valueOf(notif_share)); + params.put("data[alerts][poll]", String.valueOf(notif_poll)); + params.put("subscription[endpoint]", endpoint); + try { + String response = new HttpsConnection(context, this.instance).put(getAbsoluteUrl("/push/subscription"), 10, params, prefKeyOauthTokenT); + pushSubscription = parsePushNotifications(new JSONObject(response)); + Log.v(Helper.TAG, "response3: " + response); + } catch (HttpsConnection.HttpsConnectionException e) { + setError(e.getStatusCode(), e); + e.printStackTrace(); + } catch (NoSuchAlgorithmException | IOException | KeyManagementException | JSONException e) { + e.printStackTrace(); + } + apiResponse.setPushSubscription(pushSubscription); + return apiResponse; + } + /** * Subscribe to push notifications * @@ -5813,7 +5871,7 @@ public class API { params.put("subscription[keys][p256dh]", pubKey); params.put("subscription[keys][auth]", auth); try { - String response = new HttpsConnection(context, this.instance).post(getAbsoluteUrl("/push/subscription"), 10, params, account.getToken()); + String response = new HttpsConnection(context, this.instance).post(getAbsoluteUrl("/push/subscription"), 10, params, prefKeyOauthTokenT); pushSubscription = parsePushNotifications(new JSONObject(response)); Log.v(Helper.TAG, "response: " + response); ecdh.saveServerKey(context, account, pushSubscription.getServer_key()); From 5215576c8fe37d649cc0b6bffb5771eef150079a Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 26 Feb 2021 15:24:57 +0100 Subject: [PATCH 13/35] Generate ECDH with SC key pair --- app/build.gradle | 5 +- .../asynctasks/PostSubscriptionAsyncTask.java | 6 +- .../java/app/fedilab/android/client/API.java | 66 +++++++++++++++---- .../android/client/HttpsConnection.java | 2 + .../java/app/fedilab/android/helper/ECDH.java | 50 ++++++++++---- 5 files changed, 99 insertions(+), 30 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 997a85f7a..47c462ded 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -173,10 +173,9 @@ dependencies { //debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.2' implementation 'com.huangyz0918:androidwm-light:0.1.2' + implementation "com.madgag.spongycastle:bctls-jdk15on:1.58.0.0" - implementation "com.madgag.spongycastle:prov:1.58.0.0" - implementation "com.madgag.spongycastle:bcpkix-jdk15on:1.58.0.0" - implementation "com.madgag.spongycastle:bcpg-jdk15on:1.58.0.0" + //implementation 'org.bouncycastle:bcprov-jdk15on:1.64' implementation 'com.github.UnifiedPush:android-connector:1.0.0' //Flavors diff --git a/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java b/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java index 1e183cc25..1dbd6c446 100644 --- a/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java +++ b/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java @@ -48,11 +48,11 @@ public class PostSubscriptionAsyncTask { protected void doInBackground() { new Thread(() -> { apiResponse = new API(contextReference.get(), account.getInstance(), account.getToken()).getPushSubscription(); - if (apiResponse == null || apiResponse.getPushSubscription() == null) { + if (apiResponse == null || apiResponse.getPushSubscription() == null || endpoint.compareTo(apiResponse.getPushSubscription().getEndpoint()) != 0) { apiResponse = new API(contextReference.get(), account.getInstance(), account.getToken()).pushSubscription(endpoint, account); - } else if (apiResponse != null && endpoint.compareTo(apiResponse.getPushSubscription().getEndpoint()) != 0) { - apiResponse = new API(contextReference.get(), account.getInstance(), account.getToken()).updatePushSubscription(endpoint); } + //TODO: remove this line + apiResponse = new API(contextReference.get(), account.getInstance(), account.getToken()).pushSubscription(endpoint, account); List accountList = new ArrayList<>(); accountList.add(account); apiResponse.setAccounts(accountList); diff --git a/app/src/main/java/app/fedilab/android/client/API.java b/app/src/main/java/app/fedilab/android/client/API.java index d99111eb3..a360a9ca3 100644 --- a/app/src/main/java/app/fedilab/android/client/API.java +++ b/app/src/main/java/app/fedilab/android/client/API.java @@ -27,7 +27,7 @@ import android.util.Log; import androidx.localbroadcastmanager.content.LocalBroadcastManager; -import com.google.gson.Gson; + import com.google.gson.GsonBuilder; import com.google.gson.JsonArray; import com.google.gson.JsonObject; @@ -5790,8 +5790,8 @@ public class API { PushSubscription pushSubscription = null; try { String response = new HttpsConnection(context, this.instance).get(getAbsoluteUrl("/push/subscription"), 10, null, prefKeyOauthTokenT); + Log.v(Helper.TAG, "response GET: " + response); pushSubscription = parsePushNotifications(new JSONObject(response)); - Log.v(Helper.TAG, "response2: " + response); } catch (HttpsConnection.HttpsConnectionException e) { setError(e.getStatusCode(), e); e.printStackTrace(); @@ -5818,14 +5818,21 @@ public class API { boolean notif_status = sharedpreferences.getBoolean(Helper.SET_NOTIF_STATUS, true); boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true); + HashMap params = new HashMap<>(); + try { + endpoint = URLEncoder.encode(endpoint, "UTF-8"); + } catch (UnsupportedEncodingException ignored) { + } + params.put("subscription[endpoint]", endpoint); params.put("data[alerts][follow]", String.valueOf(notif_follow)); params.put("data[alerts][mention]", String.valueOf(notif_mention)); params.put("data[alerts][favourite]", String.valueOf(notif_add)); params.put("data[alerts][reblog]", String.valueOf(notif_share)); params.put("data[alerts][poll]", String.valueOf(notif_poll)); - params.put("subscription[endpoint]", endpoint); + try { + String response = new HttpsConnection(context, this.instance).put(getAbsoluteUrl("/push/subscription"), 10, params, prefKeyOauthTokenT); pushSubscription = parsePushNotifications(new JSONObject(response)); Log.v(Helper.TAG, "response3: " + response); @@ -5855,23 +5862,56 @@ public class API { boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true); HashMap params = new HashMap<>(); - params.put("data[alerts][follow]", String.valueOf(notif_follow)); - params.put("data[alerts][mention]", String.valueOf(notif_mention)); - params.put("data[alerts][favourite]", String.valueOf(notif_add)); - params.put("data[alerts][reblog]", String.valueOf(notif_share)); - params.put("data[alerts][poll]", String.valueOf(notif_poll)); - params.put("subscription[endpoint]", endpoint); + /*try { + endpoint = URLEncoder.encode(endpoint, "UTF-8"); + } catch (UnsupportedEncodingException ignored) { + }*/ + ECDH ecdh = ECDH.getInstance(); String pubKey = ecdh.getPublicKey(context); byte[] randBytes = new byte[16]; new Random().nextBytes(randBytes); - String auth = Base64.encodeToString(randBytes, Base64.DEFAULT); - params.put("subscription[keys][p256dh]", pubKey); - params.put("subscription[keys][auth]", auth); + String auth = ECDH.base64Encode(randBytes); + + /*try { + pubKey = URLEncoder.encode(pubKey.replaceAll("\n",""), "UTF-8"); + } catch (UnsupportedEncodingException ignored) { + } try { - String response = new HttpsConnection(context, this.instance).post(getAbsoluteUrl("/push/subscription"), 10, params, prefKeyOauthTokenT); + auth = URLEncoder.encode(auth.replaceAll("\n",""), "UTF-8"); + } catch (UnsupportedEncodingException ignored) { + }*/ + + JSONObject jsonObject = new JSONObject(); + + + try { + JSONObject jsonObjectSub = new JSONObject(); + + jsonObjectSub.put("endpoint", endpoint); + JSONObject jsonObjectkey = new JSONObject(); + jsonObjectkey.put("p256dh", pubKey); + jsonObjectkey.put("auth", auth); + jsonObjectSub.put("keys", jsonObjectkey); + jsonObject.put("subscription", jsonObjectSub); + + JSONObject jsonObjectdata = new JSONObject(); + JSONObject jsonObjectarlerts = new JSONObject(); + jsonObjectarlerts.put("follow", notif_follow); + jsonObjectarlerts.put("mention", notif_mention); + jsonObjectarlerts.put("favourite", notif_add); + jsonObjectarlerts.put("reblog", notif_share); + jsonObjectarlerts.put("poll", notif_poll); + jsonObjectdata.put("alerts", jsonObjectarlerts); + jsonObject.put("data", jsonObjectdata); + } catch (JSONException e) { + e.printStackTrace(); + } + Log.v(Helper.TAG, "jsonObject: " + jsonObject); + try { + String response = new HttpsConnection(context, this.instance).postJson(getAbsoluteUrl("/push/subscription"), 10, jsonObject, prefKeyOauthTokenT); pushSubscription = parsePushNotifications(new JSONObject(response)); Log.v(Helper.TAG, "response: " + response); ecdh.saveServerKey(context, account, pushSubscription.getServer_key()); diff --git a/app/src/main/java/app/fedilab/android/client/HttpsConnection.java b/app/src/main/java/app/fedilab/android/client/HttpsConnection.java index 0b71fc085..a82e76cd3 100644 --- a/app/src/main/java/app/fedilab/android/client/HttpsConnection.java +++ b/app/src/main/java/app/fedilab/android/client/HttpsConnection.java @@ -19,6 +19,7 @@ import android.content.SharedPreferences; import android.os.Build; import android.text.Html; import android.text.SpannableString; +import android.util.Log; import com.google.gson.JsonObject; @@ -368,6 +369,7 @@ public class HttpsConnection { postData.append('='); postData.append(param.getValue()); } + Log.v(Helper.TAG, "postData: " + postData); byte[] postDataBytes = postData.toString().getBytes(StandardCharsets.UTF_8); if (proxy != null) httpURLConnection = (HttpsURLConnection) url.openConnection(proxy); diff --git a/app/src/main/java/app/fedilab/android/helper/ECDH.java b/app/src/main/java/app/fedilab/android/helper/ECDH.java index 784bd3f37..f6917b6b6 100644 --- a/app/src/main/java/app/fedilab/android/helper/ECDH.java +++ b/app/src/main/java/app/fedilab/android/helper/ECDH.java @@ -8,7 +8,15 @@ import android.util.Log; import androidx.preference.PreferenceManager; +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.x9.ECNamedCurveTable; +import org.spongycastle.asn1.x9.X9ECParameters; +import org.spongycastle.crypto.params.ECNamedDomainParameters; +import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.spongycastle.math.ec.ECCurve; +import org.spongycastle.math.ec.ECPoint; +import java.math.BigInteger; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -37,14 +45,15 @@ public class ECDH { private static final String TAG = ECDH.class.getSimpleName(); - private static final String PROVIDER = "SC"; + private static final String PROVIDER = org.spongycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME; private static final String KEGEN_ALG = "ECDH"; private static ECDH instance; static { - Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1); + // Security.removeProvider("BC"); + Security.addProvider(new org.spongycastle.jce.provider.BouncyCastleProvider()); } private final KeyFactory kf; @@ -68,8 +77,11 @@ public class ECDH { return instance; } - static String base64Encode(byte[] b) { - return Base64.encodeToString(b, Base64.DEFAULT); + public static String base64Encode(byte[] b) { + + byte[] encoded = Base64.encode( + b, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP); + return new String(encoded); } static byte[] base64Decode(String str) { @@ -78,7 +90,7 @@ public class ECDH { synchronized KeyPair generateKeyPair() throws Exception { - ECGenParameterSpec ecParamSpec = new ECGenParameterSpec("secp256k1"); + ECGenParameterSpec ecParamSpec = new ECGenParameterSpec("prime256v1"); kpg.initialize(ecParamSpec); return kpg.generateKeyPair(); @@ -119,17 +131,29 @@ public class ECDH { KeyPair newPair(Context context) { SharedPreferences.Editor prefsEditor = PreferenceManager .getDefaultSharedPreferences(context).edit(); - - KeyPair kp = null; - + KeyPair kp; try { kp = generateKeyPair(); } catch (Exception e) { e.printStackTrace(); return null; } + ECPublicKey key = (ECPublicKey) kp.getPublic(); + byte[] x = key.getW().getAffineX().toByteArray(); + byte[] y = key.getW().getAffineY().toByteArray(); + BigInteger xbi = new BigInteger(1, x); + BigInteger ybi = new BigInteger(1, y); + X9ECParameters x9 = ECNamedCurveTable.getByName("prime256v1"); + ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID("prime256v1"); + ECCurve curve = x9.getCurve(); + ECPoint point = curve.createPoint(xbi, ybi); + ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid, + x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed()); + ECPublicKeyParameters pubKey = new ECPublicKeyParameters(point, dParams); - prefsEditor.putString(kp_public, base64Encode(kp.getPublic().getEncoded())); + String keyString = base64Encode(pubKey.getQ().getEncoded(false)); + + prefsEditor.putString(kp_public, keyString); prefsEditor.putString(kp_private, base64Encode(kp.getPrivate().getEncoded())); prefsEditor.commit(); return kp; @@ -142,7 +166,7 @@ public class ECDH { String strPub = prefs.getString(kp_public, ""); String strPriv = prefs.getString(kp_private, ""); - if (strPub.isEmpty() || strPriv.isEmpty()) { + if (strPub.isEmpty() || strPriv.isEmpty() || 1 == 1) { return newPair(context); } try { @@ -154,7 +178,11 @@ public class ECDH { } public String getPublicKey(Context context) { - return base64Encode(getPair(context).getPublic().getEncoded()); + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); + + String strPub = prefs.getString(kp_public, ""); + return strPub; } public void saveServerKey(Context context, Account account, String strPeerPublic) { From f39ec92bcfea5733e4fcd4c387f5078a3e8f2a8d Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 26 Feb 2021 19:03:36 +0100 Subject: [PATCH 14/35] some improvements --- .../java/app/fedilab/android/client/API.java | 22 ------------------- .../java/app/fedilab/android/helper/ECDH.java | 5 +++-- .../android/helper/PushNotifications.java | 13 +++++++++-- .../android/services/UnifiedPushService.java | 7 +----- 4 files changed, 15 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/client/API.java b/app/src/main/java/app/fedilab/android/client/API.java index a360a9ca3..1e10afbc7 100644 --- a/app/src/main/java/app/fedilab/android/client/API.java +++ b/app/src/main/java/app/fedilab/android/client/API.java @@ -5790,7 +5790,6 @@ public class API { PushSubscription pushSubscription = null; try { String response = new HttpsConnection(context, this.instance).get(getAbsoluteUrl("/push/subscription"), 10, null, prefKeyOauthTokenT); - Log.v(Helper.TAG, "response GET: " + response); pushSubscription = parsePushNotifications(new JSONObject(response)); } catch (HttpsConnection.HttpsConnectionException e) { setError(e.getStatusCode(), e); @@ -5835,7 +5834,6 @@ public class API { String response = new HttpsConnection(context, this.instance).put(getAbsoluteUrl("/push/subscription"), 10, params, prefKeyOauthTokenT); pushSubscription = parsePushNotifications(new JSONObject(response)); - Log.v(Helper.TAG, "response3: " + response); } catch (HttpsConnection.HttpsConnectionException e) { setError(e.getStatusCode(), e); e.printStackTrace(); @@ -5861,32 +5859,14 @@ public class API { boolean notif_status = sharedpreferences.getBoolean(Helper.SET_NOTIF_STATUS, true); boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true); - HashMap params = new HashMap<>(); - - /*try { - endpoint = URLEncoder.encode(endpoint, "UTF-8"); - } catch (UnsupportedEncodingException ignored) { - }*/ - ECDH ecdh = ECDH.getInstance(); String pubKey = ecdh.getPublicKey(context); byte[] randBytes = new byte[16]; new Random().nextBytes(randBytes); String auth = ECDH.base64Encode(randBytes); - - /*try { - pubKey = URLEncoder.encode(pubKey.replaceAll("\n",""), "UTF-8"); - } catch (UnsupportedEncodingException ignored) { - } - try { - auth = URLEncoder.encode(auth.replaceAll("\n",""), "UTF-8"); - } catch (UnsupportedEncodingException ignored) { - }*/ - JSONObject jsonObject = new JSONObject(); - try { JSONObject jsonObjectSub = new JSONObject(); @@ -5909,11 +5889,9 @@ public class API { } catch (JSONException e) { e.printStackTrace(); } - Log.v(Helper.TAG, "jsonObject: " + jsonObject); try { String response = new HttpsConnection(context, this.instance).postJson(getAbsoluteUrl("/push/subscription"), 10, jsonObject, prefKeyOauthTokenT); pushSubscription = parsePushNotifications(new JSONObject(response)); - Log.v(Helper.TAG, "response: " + response); ecdh.saveServerKey(context, account, pushSubscription.getServer_key()); } catch (HttpsConnection.HttpsConnectionException e) { setError(e.getStatusCode(), e); diff --git a/app/src/main/java/app/fedilab/android/helper/ECDH.java b/app/src/main/java/app/fedilab/android/helper/ECDH.java index f6917b6b6..3e4cda9f0 100644 --- a/app/src/main/java/app/fedilab/android/helper/ECDH.java +++ b/app/src/main/java/app/fedilab/android/helper/ECDH.java @@ -51,6 +51,7 @@ public class ECDH { private static ECDH instance; + static { // Security.removeProvider("BC"); Security.addProvider(new org.spongycastle.jce.provider.BouncyCastleProvider()); @@ -85,7 +86,7 @@ public class ECDH { } static byte[] base64Decode(String str) { - return Base64.decode(str, Base64.DEFAULT); + return Base64.decode(str, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP); } synchronized KeyPair generateKeyPair() @@ -166,7 +167,7 @@ public class ECDH { String strPub = prefs.getString(kp_public, ""); String strPriv = prefs.getString(kp_private, ""); - if (strPub.isEmpty() || strPriv.isEmpty() || 1 == 1) { + if (strPub.isEmpty() || strPriv.isEmpty()) { return newPair(context); } try { diff --git a/app/src/main/java/app/fedilab/android/helper/PushNotifications.java b/app/src/main/java/app/fedilab/android/helper/PushNotifications.java index 27e7f4ae4..451787fd7 100644 --- a/app/src/main/java/app/fedilab/android/helper/PushNotifications.java +++ b/app/src/main/java/app/fedilab/android/helper/PushNotifications.java @@ -8,6 +8,7 @@ import android.util.Log; import org.unifiedpush.android.connector.Registration; +import java.util.ArrayList; import java.util.List; import java.util.Random; @@ -43,9 +44,17 @@ public class PushNotifications implements OnPostSubscription { } } - public void displayNotification(Context context, Account account, String ciphered) { + public void displayNotification(Context context, String ciphered) { ECDH ecdh = new ECDH(); - byte[] secret = ecdh.getSecret(context, account); + + SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + List accountPush = new AccountDAO(context, db).getPushNotificationAccounts(); + List trousseau = new ArrayList<>(); + for (Account account : accountPush) { + byte[] secret = ecdh.getSecret(context, account); + } + + //process with the event // https://openacs.org/webpush-demo/report.html // decrypt using AES 128 GCM diff --git a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java index d898ce87b..686c7afff 100644 --- a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java +++ b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java @@ -32,7 +32,6 @@ class handler implements MessagingReceiverHandler { @Override public void onNewEndpoint(@Nullable Context context, @NotNull String endpoint) { - Log.v(Helper.TAG, "onNewEndpoint: " + endpoint); final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); SharedPreferences.Editor editor = sharedpreferences.edit(); editor.putString(Helper.SERVER_ENDPOINT, endpoint); @@ -44,27 +43,23 @@ class handler implements MessagingReceiverHandler { @Override public void onRegistrationFailed(@Nullable Context context) { - Log.v(Helper.TAG, "onRegistrationFailed: "); // Toast ? } @Override public void onRegistrationRefused(@Nullable Context context) { // Toast ? - Log.v(Helper.TAG, "onRegistrationRefused: "); } @Override public void onUnregistered(@Nullable Context context) { - Log.v(Helper.TAG, "onUnregistered: "); // Remove endpoint & ServerKey } @Override public void onMessage(@Nullable Context context, @NotNull String message) { - Log.v(Helper.TAG, "Message: " + message); new PushNotifications() - .displayNotification(context, null, message); + .displayNotification(context, message); } } From 67499ae66840d16af0238989d0056feecfd7947e Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 28 Feb 2021 09:54:57 +0100 Subject: [PATCH 15/35] copy release notes --- .../interfaces/OnPostSubscription.java | 21 ------------------- 1 file changed, 21 deletions(-) delete mode 100644 app/src/main/java/app/fedilab/android/interfaces/OnPostSubscription.java diff --git a/app/src/main/java/app/fedilab/android/interfaces/OnPostSubscription.java b/app/src/main/java/app/fedilab/android/interfaces/OnPostSubscription.java deleted file mode 100644 index 42a834d6f..000000000 --- a/app/src/main/java/app/fedilab/android/interfaces/OnPostSubscription.java +++ /dev/null @@ -1,21 +0,0 @@ -/* Copyright 2021 Thomas Schneider - * - * This file is a part of Fedilab - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Fedilab; if not, - * see . */ -package app.fedilab.android.interfaces; - -import app.fedilab.android.client.APIResponse; - -public interface OnPostSubscription { - void onSubscription(APIResponse apiResponse); -} From fc21e7ebcc7d35a673f174ba07b0459790510e7f Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 28 Feb 2021 10:38:49 +0100 Subject: [PATCH 16/35] Some changes --- app/build.gradle | 2 +- .../android/activities/BaseMainActivity.java | 3 +- .../asynctasks/PostSubscriptionAsyncTask.java | 17 +- .../java/app/fedilab/android/helper/ECDH.java | 157 ++++++++++++------ .../android/helper/PushNotifications.java | 57 ++----- .../android/services/UnifiedPushService.java | 55 +++--- 6 files changed, 152 insertions(+), 139 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 47c462ded..962c6db73 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -177,7 +177,7 @@ dependencies { implementation "com.madgag.spongycastle:bctls-jdk15on:1.58.0.0" //implementation 'org.bouncycastle:bcprov-jdk15on:1.64' - implementation 'com.github.UnifiedPush:android-connector:1.0.0' + implementation 'com.github.UnifiedPush:android-connector:dev-SNAPSHOT' //Flavors //Playstore diff --git a/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java index 525f7783e..313ec321c 100644 --- a/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java @@ -423,8 +423,7 @@ public abstract class BaseMainActivity extends BaseActivity if (social == UpdateAccountInfoAsyncTask.SOCIAL.MASTODON || social == UpdateAccountInfoAsyncTask.SOCIAL.PLEROMA) { - - new Registration().registerAppWithDialog(BaseMainActivity.this); + new Registration().registerAppWithDialog(BaseMainActivity.this, account.getUsername() + "@" + account.getInstance()); } if (social == UpdateAccountInfoAsyncTask.SOCIAL.MASTODON || social == UpdateAccountInfoAsyncTask.SOCIAL.PLEROMA || social == UpdateAccountInfoAsyncTask.SOCIAL.GNU || social == UpdateAccountInfoAsyncTask.SOCIAL.FRIENDICA) { new SyncTimelinesAsyncTask(BaseMainActivity.this, 0, Helper.canFetchList(BaseMainActivity.this, account), BaseMainActivity.this); diff --git a/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java b/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java index 1dbd6c446..b9acfea3e 100644 --- a/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java +++ b/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java @@ -15,30 +15,23 @@ package app.fedilab.android.asynctasks; import android.content.Context; -import android.os.Handler; -import android.os.Looper; import java.lang.ref.WeakReference; -import java.util.ArrayList; -import java.util.List; import app.fedilab.android.client.API; import app.fedilab.android.client.APIResponse; import app.fedilab.android.client.Entities.Account; -import app.fedilab.android.interfaces.OnPostSubscription; public class PostSubscriptionAsyncTask { - private final OnPostSubscription listener; private final WeakReference contextReference; private APIResponse apiResponse; private final String endpoint; private final Account account; - public PostSubscriptionAsyncTask(Context context, Account account, String endpoint, OnPostSubscription onPostSubscription) { + public PostSubscriptionAsyncTask(Context context, Account account, String endpoint) { this.contextReference = new WeakReference<>(context); - this.listener = onPostSubscription; this.endpoint = endpoint; this.account = account; doInBackground(); @@ -51,14 +44,6 @@ public class PostSubscriptionAsyncTask { if (apiResponse == null || apiResponse.getPushSubscription() == null || endpoint.compareTo(apiResponse.getPushSubscription().getEndpoint()) != 0) { apiResponse = new API(contextReference.get(), account.getInstance(), account.getToken()).pushSubscription(endpoint, account); } - //TODO: remove this line - apiResponse = new API(contextReference.get(), account.getInstance(), account.getToken()).pushSubscription(endpoint, account); - List accountList = new ArrayList<>(); - accountList.add(account); - apiResponse.setAccounts(accountList); - Handler mainHandler = new Handler(Looper.getMainLooper()); - Runnable myRunnable = () -> listener.onSubscription(apiResponse); - mainHandler.post(myRunnable); }).start(); } diff --git a/app/src/main/java/app/fedilab/android/helper/ECDH.java b/app/src/main/java/app/fedilab/android/helper/ECDH.java index 3e4cda9f0..e60173d51 100644 --- a/app/src/main/java/app/fedilab/android/helper/ECDH.java +++ b/app/src/main/java/app/fedilab/android/helper/ECDH.java @@ -1,5 +1,20 @@ package app.fedilab.android.helper; +/* Copyright 2021 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ +import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.util.Base64; @@ -12,7 +27,9 @@ import org.spongycastle.asn1.ASN1ObjectIdentifier; import org.spongycastle.asn1.x9.ECNamedCurveTable; import org.spongycastle.asn1.x9.X9ECParameters; import org.spongycastle.crypto.params.ECNamedDomainParameters; +import org.spongycastle.crypto.params.ECPrivateKeyParameters; import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.spongycastle.jce.spec.ECNamedCurveSpec; import org.spongycastle.math.ec.ECCurve; import org.spongycastle.math.ec.ECPoint; @@ -27,7 +44,6 @@ import java.security.PublicKey; import java.security.Security; import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; -import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import javax.crypto.KeyAgreement; @@ -40,25 +56,25 @@ public class ECDH { private static final String kp_public = "kp_public"; + public static final String peer_public = "peer_public"; + public static final String PROVIDER = org.spongycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME; + private static final String kp_private = "kp_private"; - private static final String peer_public = "peer_public"; + public static final String KEGEN_ALG = "ECDH"; private static final String TAG = ECDH.class.getSimpleName(); - - private static final String PROVIDER = org.spongycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME; - - private static final String KEGEN_ALG = "ECDH"; + private static final String kp_public_affine_x = "kp_public_affine_x"; + private static final String kp_public_affine_y = "kp_public_affine_y"; private static ECDH instance; static { - // Security.removeProvider("BC"); Security.addProvider(new org.spongycastle.jce.provider.BouncyCastleProvider()); } - private final KeyFactory kf; - private final KeyPairGenerator kpg; + public static KeyFactory kf = null; + private static KeyPairGenerator kpg = null; public ECDH() { @@ -89,7 +105,7 @@ public class ECDH { return Base64.decode(str, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP); } - synchronized KeyPair generateKeyPair() + static synchronized KeyPair generateKeyPair() throws Exception { ECGenParameterSpec ecParamSpec = new ECGenParameterSpec("prime256v1"); kpg.initialize(ecParamSpec); @@ -97,7 +113,7 @@ public class ECDH { return kpg.generateKeyPair(); } - byte[] generateSecret(PrivateKey myPrivKey, PublicKey otherPubKey) throws Exception { + private static byte[] generateSecret(PrivateKey myPrivKey, PublicKey otherPubKey) throws Exception { ECPublicKey ecPubKey = (ECPublicKey) otherPubKey; Log.d(TAG, "public key Wx: " + ecPubKey.getW().getAffineX().toString(16)); @@ -111,25 +127,14 @@ public class ECDH { return keyAgreement.generateSecret(); } - synchronized PublicKey readPublicKey(String keyStr) throws Exception { - X509EncodedKeySpec x509ks = new X509EncodedKeySpec( - base64Decode(keyStr)); - return kf.generatePublic(x509ks); - } - synchronized PrivateKey readPrivateKey(String keyStr) throws Exception { - PKCS8EncodedKeySpec p8ks = new PKCS8EncodedKeySpec( - base64Decode(keyStr)); - - return kf.generatePrivate(p8ks); - } - - synchronized KeyPair readKeyPair(String pubKeyStr, String privKeyStr) + static synchronized KeyPair readKeyPair(Context context) throws Exception { - return new KeyPair(readPublicKey(pubKeyStr), readPrivateKey(privKeyStr)); + return new KeyPair(readMyPublicKey(context), readMyPrivateKey(context)); } - KeyPair newPair(Context context) { + @SuppressLint("ApplySharedPref") + static KeyPair newPair(Context context) { SharedPreferences.Editor prefsEditor = PreferenceManager .getDefaultSharedPreferences(context).edit(); KeyPair kp; @@ -139,6 +144,7 @@ public class ECDH { e.printStackTrace(); return null; } + ECPublicKey key = (ECPublicKey) kp.getPublic(); byte[] x = key.getW().getAffineX().toByteArray(); byte[] y = key.getW().getAffineY().toByteArray(); @@ -146,62 +152,95 @@ public class ECDH { BigInteger ybi = new BigInteger(1, y); X9ECParameters x9 = ECNamedCurveTable.getByName("prime256v1"); ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID("prime256v1"); + ECCurve curve = x9.getCurve(); ECPoint point = curve.createPoint(xbi, ybi); ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid, x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed()); + ECPublicKeyParameters pubKey = new ECPublicKeyParameters(point, dParams); - String keyString = base64Encode(pubKey.getQ().getEncoded(false)); + ECPrivateKeyParameters privateKey = new ECPrivateKeyParameters(new BigInteger(kp.getPrivate().getEncoded()), pubKey.getParameters()); + byte[] privateKeyBytes = privateKey.getD().toByteArray(); + + String keyString = base64Encode(pubKey.getQ().getEncoded(false)); + String keypString = base64Encode(privateKeyBytes); prefsEditor.putString(kp_public, keyString); - prefsEditor.putString(kp_private, base64Encode(kp.getPrivate().getEncoded())); + prefsEditor.putString(kp_public_affine_x, key.getW().getAffineX().toString()); + prefsEditor.putString(kp_public_affine_y, key.getW().getAffineY().toString()); + prefsEditor.putString(kp_private, keypString); prefsEditor.commit(); return kp; } - synchronized KeyPair getPair(Context context) { + + static synchronized PublicKey readMyPublicKey(Context context) throws Exception { + + X9ECParameters x9 = ECNamedCurveTable.getByName("prime256v1"); + ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID("prime256v1"); + SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(context); + BigInteger xbi = new BigInteger(prefs.getString(kp_public_affine_x, "0")); + BigInteger ybi = new BigInteger(prefs.getString(kp_public_affine_y, "0")); + ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid, + x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed()); + + + ECNamedCurveSpec ecNamedCurveSpec = new ECNamedCurveSpec("prime256v1", dParams.getCurve(), dParams.getG(), dParams.getN()); + java.security.spec.ECPoint w = new java.security.spec.ECPoint(xbi, ybi); + return kf.generatePublic(new java.security.spec.ECPublicKeySpec(w, ecNamedCurveSpec)); + } + + + static synchronized PublicKey readPublicKey(String keyStr) throws Exception { + X509EncodedKeySpec x509ks = new X509EncodedKeySpec( + base64Decode(keyStr)); + return kf.generatePublic(x509ks); + } + + + static synchronized PrivateKey readMyPrivateKey(Context context) throws Exception { + X9ECParameters x9 = ECNamedCurveTable.getByName("prime256v1"); + ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID("prime256v1"); + + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); + BigInteger ybi = new BigInteger(prefs.getString(kp_public_affine_y, "0")); + ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid, + x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed()); + ECNamedCurveSpec ecNamedCurveSpec = new ECNamedCurveSpec("prime256v1", dParams.getCurve(), dParams.getG(), dParams.getN()); + return kf.generatePrivate(new java.security.spec.ECPrivateKeySpec(ybi, ecNamedCurveSpec)); + } + + + private static synchronized KeyPair getPair(Context context) { + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); String strPub = prefs.getString(kp_public, ""); String strPriv = prefs.getString(kp_private, ""); - if (strPub.isEmpty() || strPriv.isEmpty()) { return newPair(context); } try { - return readKeyPair(strPub, strPriv); + return readKeyPair(context); } catch (Exception e) { e.printStackTrace(); } return null; } - public String getPublicKey(Context context) { + static PublicKey getServerKey(Context context, Account account) throws Exception { SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(context); - - String strPub = prefs.getString(kp_public, ""); - return strPub; + String serverKey = prefs.getString(peer_public + account.getId() + account.getInstance(), ""); + return readPublicKey(serverKey); } - public void saveServerKey(Context context, Account account, String strPeerPublic) { - SharedPreferences.Editor prefsEditor = PreferenceManager - .getDefaultSharedPreferences(context).edit(); - - prefsEditor.putString(peer_public + account.getId() + account.getInstance(), strPeerPublic); - prefsEditor.commit(); - } - - PublicKey getServerKey(Context context, Account account) throws Exception { - return readPublicKey( - PreferenceManager.getDefaultSharedPreferences(context) - .getString(peer_public + account.getId() + account.getInstance(), "") - ); - } - - byte[] getSecret(Context context, Account account) { + @SuppressWarnings({"unused", "RedundantSuppression"}) + public static byte[] getSharedSecret(Context context, Account account) { try { return generateSecret(getPair(context).getPrivate(), getServerKey(context, account)); } catch (Exception e) { @@ -209,4 +248,20 @@ public class ECDH { return null; } } + + public String getPublicKey(Context context) { + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); + + return prefs.getString(kp_public, ""); + } + + @SuppressLint("ApplySharedPref") + public void saveServerKey(Context context, Account account, String strPeerPublic) { + SharedPreferences.Editor prefsEditor = PreferenceManager + .getDefaultSharedPreferences(context).edit(); + + prefsEditor.putString(peer_public + account.getId() + account.getInstance(), strPeerPublic); + prefsEditor.commit(); + } } diff --git a/app/src/main/java/app/fedilab/android/helper/PushNotifications.java b/app/src/main/java/app/fedilab/android/helper/PushNotifications.java index 451787fd7..146b68aab 100644 --- a/app/src/main/java/app/fedilab/android/helper/PushNotifications.java +++ b/app/src/main/java/app/fedilab/android/helper/PushNotifications.java @@ -1,67 +1,44 @@ package app.fedilab.android.helper; - +/* Copyright 2021 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ import android.content.Context; -import android.content.SharedPreferences; import android.database.sqlite.SQLiteDatabase; -import android.util.Base64; -import android.util.Log; -import org.unifiedpush.android.connector.Registration; -import java.util.ArrayList; import java.util.List; -import java.util.Random; - -import app.fedilab.android.activities.BaseMainActivity; import app.fedilab.android.asynctasks.PostSubscriptionAsyncTask; -import app.fedilab.android.client.APIResponse; + import app.fedilab.android.client.Entities.Account; -import app.fedilab.android.interfaces.OnPostSubscription; import app.fedilab.android.sqlite.AccountDAO; import app.fedilab.android.sqlite.Sqlite; -import static android.content.Context.MODE_PRIVATE; -public class PushNotifications implements OnPostSubscription { +public class PushNotifications { - private Context context; public void registerPushNotifications(Context context, String endpoint) { - this.context = context; SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); List accountPush = new AccountDAO(context, db).getPushNotificationAccounts(); for (Account account : accountPush) { - new PostSubscriptionAsyncTask(context, account, endpoint, this); + new PostSubscriptionAsyncTask(context, account, endpoint); } } - public static void getDistributors(Context context) { - List distributors = new Registration().getDistributors(context); - if (distributors.isEmpty()) { - - } else { - - } - } public void displayNotification(Context context, String ciphered) { - ECDH ecdh = new ECDH(); - SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); - List accountPush = new AccountDAO(context, db).getPushNotificationAccounts(); - List trousseau = new ArrayList<>(); - for (Account account : accountPush) { - byte[] secret = ecdh.getSecret(context, account); - } - - - //process with the event - // https://openacs.org/webpush-demo/report.html - // decrypt using AES 128 GCM } - @Override - public void onSubscription(APIResponse apiResponse) { - //TODO je ne sais pas si c'est toujours utile - } } diff --git a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java index 686c7afff..8595731da 100644 --- a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java +++ b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java @@ -1,37 +1,40 @@ package app.fedilab.android.services; - +/* Copyright 2021 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ import android.content.Context; import android.content.SharedPreferences; -import android.database.sqlite.SQLiteDatabase; -import android.util.Log; - import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.unifiedpush.android.connector.MessagingReceiver; import org.unifiedpush.android.connector.MessagingReceiverHandler; - -import java.util.List; - -import app.fedilab.android.activities.BaseMainActivity; -import app.fedilab.android.activities.LiveNotificationSettingsAccountsActivity; -import app.fedilab.android.asynctasks.PostSubscriptionAsyncTask; -import app.fedilab.android.client.APIResponse; -import app.fedilab.android.client.Entities.Account; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.PushNotifications; -import app.fedilab.android.interfaces.OnPostSubscription; -import app.fedilab.android.sqlite.AccountDAO; -import app.fedilab.android.sqlite.Sqlite; import static android.content.Context.MODE_PRIVATE; class handler implements MessagingReceiverHandler { - private Context context; - private String endpoint; @Override - public void onNewEndpoint(@Nullable Context context, @NotNull String endpoint) { + public void onMessage(@Nullable Context context, @NotNull String s, @NotNull String s1) { + new PushNotifications() + .displayNotification(context, s); + } + + @Override + public void onNewEndpoint(@Nullable Context context, @NotNull String endpoint, @NotNull String s1) { final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); SharedPreferences.Editor editor = sharedpreferences.edit(); editor.putString(Helper.SERVER_ENDPOINT, endpoint); @@ -42,24 +45,18 @@ class handler implements MessagingReceiverHandler { } @Override - public void onRegistrationFailed(@Nullable Context context) { - // Toast ? + public void onRegistrationFailed(@Nullable Context context, @NotNull String s) { + } @Override - public void onRegistrationRefused(@Nullable Context context) { - // Toast ? + public void onRegistrationRefused(@Nullable Context context, @NotNull String s) { + } @Override - public void onUnregistered(@Nullable Context context) { - // Remove endpoint & ServerKey - } + public void onUnregistered(@Nullable Context context, @NotNull String s) { - @Override - public void onMessage(@Nullable Context context, @NotNull String message) { - new PushNotifications() - .displayNotification(context, message); } } From 0a3a68e0a75c48c9a3cad1703a68407a8fa9fe37 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 28 Feb 2021 10:54:50 +0100 Subject: [PATCH 17/35] copy release notes --- .../android/helper/NotificationsHelper.java | 4 + .../services/BaseLiveNotificationService.java | 536 ------------------ .../LiveNotificationDelayedService.java | 520 ----------------- .../RestartLiveNotificationReceiver.java | 45 -- .../StopDelayedNotificationReceiver.java | 41 -- .../StopLiveNotificationReceiver.java | 41 -- 6 files changed, 4 insertions(+), 1183 deletions(-) create mode 100644 app/src/main/java/app/fedilab/android/helper/NotificationsHelper.java delete mode 100644 app/src/main/java/app/fedilab/android/services/BaseLiveNotificationService.java delete mode 100644 app/src/main/java/app/fedilab/android/services/LiveNotificationDelayedService.java delete mode 100644 app/src/main/java/app/fedilab/android/services/RestartLiveNotificationReceiver.java delete mode 100644 app/src/main/java/app/fedilab/android/services/StopDelayedNotificationReceiver.java delete mode 100644 app/src/main/java/app/fedilab/android/services/StopLiveNotificationReceiver.java diff --git a/app/src/main/java/app/fedilab/android/helper/NotificationsHelper.java b/app/src/main/java/app/fedilab/android/helper/NotificationsHelper.java new file mode 100644 index 000000000..628933454 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/helper/NotificationsHelper.java @@ -0,0 +1,4 @@ +package app.fedilab.android.helper; + +class NotificationsHelper { +} diff --git a/app/src/main/java/app/fedilab/android/services/BaseLiveNotificationService.java b/app/src/main/java/app/fedilab/android/services/BaseLiveNotificationService.java deleted file mode 100644 index 0665bb38d..000000000 --- a/app/src/main/java/app/fedilab/android/services/BaseLiveNotificationService.java +++ /dev/null @@ -1,536 +0,0 @@ -package app.fedilab.android.services; -/* Copyright 2017 Thomas Schneider - * - * This file is a part of Fedilab - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Fedilab; if not, - * see . */ - -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -import android.database.sqlite.SQLiteDatabase; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.Drawable; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.preference.PreferenceManager; -import android.text.Html; -import android.text.SpannableString; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.app.NotificationCompat; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.load.DataSource; -import com.bumptech.glide.load.engine.GlideException; -import com.bumptech.glide.request.RequestListener; -import com.bumptech.glide.request.target.CustomTarget; -import com.bumptech.glide.request.target.Target; -import com.bumptech.glide.request.transition.Transition; -import com.koushikdutta.async.http.AsyncHttpClient; -import com.koushikdutta.async.http.AsyncHttpRequest; -import com.koushikdutta.async.http.Headers; -import com.koushikdutta.async.http.WebSocket; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.HashMap; -import java.util.List; -import java.util.Objects; - -import app.fedilab.android.R; -import app.fedilab.android.activities.MainActivity; -import app.fedilab.android.client.API; -import app.fedilab.android.client.Entities.Account; -import app.fedilab.android.client.Entities.Notification; -import app.fedilab.android.helper.Helper; -import app.fedilab.android.sqlite.AccountDAO; -import app.fedilab.android.sqlite.Sqlite; - -import static androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY; -import static app.fedilab.android.helper.Helper.getMainLogo; -import static app.fedilab.android.helper.Helper.getNotificationIcon; - - -/** - * Created by Thomas on 29/11/2017. - * Manage service for streaming api and new notifications - */ - -public abstract class BaseLiveNotificationService extends Service implements NetworkStateReceiver.NetworkStateReceiverListener { - - public static String CHANNEL_ID = "live_notifications"; - public static int totalAccount = 0; - public static int eventsCount = 0; - private static final HashMap lastNotification = new HashMap<>(); - private static final HashMap webSocketFutures = new HashMap<>(); - - - protected Account account; - private NetworkStateReceiver networkStateReceiver; - private NotificationChannel channel; - - public void onCreate() { - super.onCreate(); - final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - boolean notify = sharedpreferences.getBoolean(Helper.SET_NOTIFY, true); - networkStateReceiver = new NetworkStateReceiver(); - networkStateReceiver.addListener(this); - registerReceiver(networkStateReceiver, new IntentFilter(android.net.ConnectivityManager.CONNECTIVITY_ACTION)); - - - if (Build.VERSION.SDK_INT >= 26) { - channel = new NotificationChannel(CHANNEL_ID, - "Live notifications", - NotificationManager.IMPORTANCE_DEFAULT); - - ((NotificationManager) Objects.requireNonNull(getSystemService(Context.NOTIFICATION_SERVICE))).createNotificationChannel(channel); - } - SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); - List accountStreams = new AccountDAO(BaseLiveNotificationService.this, db).getAllAccountCrossAction(); - totalAccount = 0; - if (accountStreams != null) { - for (Account account : accountStreams) { - if (account.getSocial() == null || account.getSocial().equals("MASTODON") || account.getSocial().equals("PLEROMA")) { - boolean allowStream = sharedpreferences.getBoolean(Helper.SET_ALLOW_STREAM + account.getId() + account.getInstance(), true); - if (allowStream) { - totalAccount++; - } - } - } - } - if (Build.VERSION.SDK_INT >= 26) { - Intent myIntent = new Intent(BaseLiveNotificationService.this, MainActivity.class); - PendingIntent pendingIntent = PendingIntent.getActivity( - BaseLiveNotificationService.this, - 0, - myIntent, - PendingIntent.FLAG_UPDATE_CURRENT); - android.app.Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID) - .setContentTitle(getString(R.string.top_notification)) - .setContentIntent(pendingIntent) - .setSmallIcon(getNotificationIcon(BaseLiveNotificationService.this)) - .setContentText(getString(R.string.top_notification_message, String.valueOf(totalAccount), String.valueOf(eventsCount))).build(); - - if (notification != null) { - startForeground(2, notification); - } else { - return; - } - } - if (!notify) { - stopSelf(); - return; - } - if (totalAccount == 0) { - stopSelf(); - } - } - - private void startStream() { - - SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - boolean liveNotifications = sharedpreferences.getBoolean(Helper.SET_LIVE_NOTIFICATIONS, true); - SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); - if (liveNotifications) { - List accountStreams = new AccountDAO(BaseLiveNotificationService.this, db).getAllAccountCrossAction(); - if (accountStreams != null) { - for (final Account accountStream : accountStreams) { - if (accountStream.getSocial() == null || accountStream.getSocial().equals("MASTODON") || accountStream.getSocial().equals("PLEROMA")) { - startWork(accountStream); - } - } - } - } - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - boolean notify = sharedpreferences.getBoolean(Helper.SET_NOTIFY, true); - - - if (!notify || intent == null || intent.getBooleanExtra("stop", false)) { - totalAccount = 0; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - stopForeground(STOP_FOREGROUND_DETACH); - NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - assert notificationManager != null; - notificationManager.deleteNotificationChannel(CHANNEL_ID); - } - if (intent != null) { - intent.replaceExtras(new Bundle()); - } - stopSelf(); - } - if (totalAccount > 0) { - return START_STICKY; - } - return START_NOT_STICKY; - } - - @Override - public void onDestroy() { - super.onDestroy(); - for (Thread t : Thread.getAllStackTraces().keySet()) { - if (t.getName().startsWith("notif_live_")) { - t.interrupt(); - } - } - Thread.currentThread().interrupt(); - if (networkStateReceiver != null) { - networkStateReceiver.removeListener(this); - unregisterReceiver(networkStateReceiver); - } - } - - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return null; - } - - - private void taks(Account account) { - - if (account != null) { - Headers headers = new Headers(); - headers.add("Authorization", "Bearer " + account.getToken()); - headers.add("Connection", "Keep-Alive"); - headers.add("method", "GET"); - headers.add("scheme", "https"); - String notif_url = "user:notification"; - if (account.getSocial().toUpperCase().equals("PLEROMA")) - notif_url = "user"; - String urlKey = "wss://" + account.getInstance() + "/api/v1/streaming/?stream=" + notif_url + "&access_token=" + account.getToken(); - Uri url = Uri.parse(urlKey); - AsyncHttpRequest.setDefaultHeaders(headers, url); - String key = account.getAcct() + "@" + account.getInstance(); - if (webSocketFutures.get(key) == null || !Objects.requireNonNull(webSocketFutures.get(key)).isOpen()) { - AsyncHttpClient.getDefaultInstance().websocket("wss://" + account.getInstance() + "/api/v1/streaming/?stream=" + notif_url + "&access_token=" + account.getToken(), "wss", (ex, webSocket) -> { - webSocketFutures.put(account.getAcct() + "@" + account.getInstance(), webSocket); - if (ex != null) { - return; - } - webSocket.setStringCallback(s -> { - try { - JSONObject eventJson = new JSONObject(s); - onRetrieveStreaming(account, eventJson); - } catch (JSONException ignored) { - } - }); - - webSocket.setClosedCallback(ex1 -> { - if (networkStateReceiver.connected) { - startWork(account); - } - }); - webSocket.setDataCallback((emitter, byteBufferList) -> { - // note that this data has been read - byteBufferList.recycle(); - }); - }); - } - } - } - - - private void startWork(Account accountStream) { - - String key = accountStream.getAcct() + "@" + accountStream.getInstance(); - Thread thread = Helper.getThreadByName("notif_live_" + key); - if (thread == null) { - thread = new Thread() { - @Override - public void run() { - taks(accountStream); - } - }; - thread.setName("notif_live_" + key); - thread.start(); - } else if (thread.getState() != Thread.State.RUNNABLE) { - thread.interrupt(); - thread = new Thread() { - @Override - public void run() { - taks(accountStream); - } - }; - thread.setName("notif_live_" + key); - thread.start(); - } - - - } - - private void onRetrieveStreaming(Account account, JSONObject response) { - if (response == null) - return; - final Notification notification; - Bundle b = new Bundle(); - boolean canSendBroadCast = true; - Helper.EventStreaming event; - final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); - try { - if ("notification".equals(response.get("event").toString())) { - eventsCount++; - if (Build.VERSION.SDK_INT >= 26) { - channel = new NotificationChannel(CHANNEL_ID, - "Live notifications", - NotificationManager.IMPORTANCE_DEFAULT); - ((NotificationManager) Objects.requireNonNull(getSystemService(Context.NOTIFICATION_SERVICE))).createNotificationChannel(channel); - android.app.Notification notificationChannel = new NotificationCompat.Builder(this, CHANNEL_ID) - .setContentTitle(getString(R.string.top_notification)) - .setSmallIcon(getNotificationIcon(BaseLiveNotificationService.this)) - .setContentText(getString(R.string.top_notification_message, String.valueOf(totalAccount), String.valueOf(eventsCount))).build(); - if (notificationChannel != null) { - startForeground(2, notificationChannel); - } else { - return; - } - } - - event = Helper.EventStreaming.NOTIFICATION; - notification = API.parseNotificationResponse(BaseLiveNotificationService.this, new JSONObject(response.get("payload").toString())); - b.putParcelable("data", notification); - boolean liveNotifications = sharedpreferences.getBoolean(Helper.SET_LIVE_NOTIFICATIONS, true); - boolean canNotify = Helper.canNotify(BaseLiveNotificationService.this); - boolean notify = sharedpreferences.getBoolean(Helper.SET_NOTIFY, true); - String targeted_account = null; - Helper.NotifType notifType = Helper.NotifType.MENTION; - boolean activityRunning = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("isMainActivityRunning", false); - String key = account.getAcct() + "@" + account.getInstance(); - if (lastNotification.containsKey(key) && notification.getId().compareTo(Objects.requireNonNull(lastNotification.get(key))) <= 0) { - canNotify = false; - } - String lastNotif = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null); - if (notification.getId().compareTo(Objects.requireNonNull(lastNotif)) <= 0) { - canNotify = false; - } - boolean allowStream = sharedpreferences.getBoolean(Helper.SET_ALLOW_STREAM + account.getId() + account.getInstance(), true); - if (!allowStream) { - canNotify = false; - } - - if ((userId == null || !userId.equals(account.getId()) || !activityRunning) && liveNotifications && canNotify && notify) { - lastNotification.put(key, notification.getId()); - boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true); - boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true); - boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true); - boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true); - boolean notif_status = sharedpreferences.getBoolean(Helper.SET_NOTIF_STATUS, true); - boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true); - boolean somethingToPush = (notif_follow || notif_add || notif_mention || notif_share || notif_poll || notif_status); - String message = null; - if (somethingToPush) { - switch (notification.getType()) { - case "mention": - notifType = Helper.NotifType.MENTION; - if (notif_mention) { - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_mention)); - else - message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_mention)); - if (notification.getStatus() != null) { - if (notification.getStatus().getSpoiler_text() != null && notification.getStatus().getSpoiler_text().length() > 0) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text(), FROM_HTML_MODE_LEGACY)); - else - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text())); - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent(), FROM_HTML_MODE_LEGACY)); - else - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent())); - } - } - } else { - canSendBroadCast = false; - } - break; - case "status": - notifType = Helper.NotifType.STATUS; - if (notif_status) { - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_status)); - else - message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_status)); - if (notification.getStatus() != null) { - if (notification.getStatus().getSpoiler_text() != null && notification.getStatus().getSpoiler_text().length() > 0) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text(), FROM_HTML_MODE_LEGACY)); - else - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text())); - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent(), FROM_HTML_MODE_LEGACY)); - else - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent())); - } - } - } else { - canSendBroadCast = false; - } - break; - case "reblog": - notifType = Helper.NotifType.BOOST; - if (notif_share) { - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_reblog)); - else - message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_reblog)); - } else { - canSendBroadCast = false; - } - break; - case "favourite": - notifType = Helper.NotifType.FAV; - if (notif_add) { - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_favourite)); - else - message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_favourite)); - } else { - canSendBroadCast = false; - } - break; - case "follow_request": - notifType = Helper.NotifType.FOLLLOW; - if (notif_follow) { - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_follow_request)); - else - message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_follow_request)); - targeted_account = notification.getAccount().getId(); - } else { - canSendBroadCast = false; - } - break; - case "follow": - notifType = Helper.NotifType.FOLLLOW; - if (notif_follow) { - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_follow)); - else - message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_follow)); - targeted_account = notification.getAccount().getId(); - } else { - canSendBroadCast = false; - } - break; - case "poll": - notifType = Helper.NotifType.POLL; - if (notif_poll) { - if (notification.getAccount().getId() != null && notification.getAccount().getId().equals(userId)) - message = getString(R.string.notif_poll_self); - else - message = getString(R.string.notif_poll); - } else { - canSendBroadCast = false; - } - break; - default: - } - //Some others notification - final Intent intent = new Intent(BaseLiveNotificationService.this, MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(Helper.INTENT_ACTION, Helper.NOTIFICATION_INTENT); - intent.putExtra(Helper.PREF_KEY_ID, account.getId()); - intent.putExtra(Helper.PREF_INSTANCE, account.getInstance()); - if (targeted_account != null) { - intent.putExtra(Helper.INTENT_TARGETED_ACCOUNT, targeted_account); - } - final String finalMessage = message; - Handler mainHandler = new Handler(Looper.getMainLooper()); - Helper.NotifType finalNotifType = notifType; - Runnable myRunnable = () -> { - if (finalMessage != null) { - Glide.with(BaseLiveNotificationService.this) - .asBitmap() - .load(notification.getAccount().getAvatar()) - .listener(new RequestListener() { - @Override - public boolean onResourceReady(Bitmap resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { - return false; - } - - @Override - public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { - assert e != null; - Helper.notify_user(BaseLiveNotificationService.this, account, intent, BitmapFactory.decodeResource(getResources(), - getMainLogo(BaseLiveNotificationService.this)), finalNotifType, "@" + notification.getAccount().getAcct(), finalMessage); - return false; - } - }) - .into(new CustomTarget() { - @Override - public void onResourceReady(@NonNull Bitmap resource, Transition transition) { - Helper.notify_user(BaseLiveNotificationService.this, account, intent, resource, finalNotifType, "@" + notification.getAccount().getAcct(), finalMessage); - } - - @Override - public void onLoadCleared(@Nullable Drawable placeholder) { - - } - }); - } - }; - mainHandler.post(myRunnable); - } - } - - if (canSendBroadCast) { - b.putString("userIdService", account.getId()); - Intent intentBC = new Intent(Helper.RECEIVE_DATA); - intentBC.putExtra("eventStreaming", event); - intentBC.putExtras(b); - b.putParcelable("data", notification); - LocalBroadcastManager.getInstance(BaseLiveNotificationService.this).sendBroadcast(intentBC); - SharedPreferences.Editor editor = sharedpreferences.edit(); - editor.putString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), notification.getId()); - editor.apply(); - } - } - } catch (Exception ignored) { - } - } - - @Override - public void networkAvailable() { - startStream(); - } - - @Override - public void networkUnavailable() { - for (Thread t : Thread.getAllStackTraces().keySet()) { - if (t.getName().startsWith("notif_live_")) { - t.interrupt(); - } - } - Thread.currentThread().interrupt(); - } -} diff --git a/app/src/main/java/app/fedilab/android/services/LiveNotificationDelayedService.java b/app/src/main/java/app/fedilab/android/services/LiveNotificationDelayedService.java deleted file mode 100644 index f4ddeeb7b..000000000 --- a/app/src/main/java/app/fedilab/android/services/LiveNotificationDelayedService.java +++ /dev/null @@ -1,520 +0,0 @@ -package app.fedilab.android.services; -/* Copyright 2019 Thomas Schneider - * - * This file is a part of Fedilab - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Fedilab; if not, - * see . */ - -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.database.sqlite.SQLiteDatabase; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.os.SystemClock; -import android.preference.PreferenceManager; -import android.text.Html; -import android.text.SpannableString; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.core.app.NotificationCompat; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.load.DataSource; -import com.bumptech.glide.load.engine.GlideException; -import com.bumptech.glide.request.RequestListener; -import com.bumptech.glide.request.target.CustomTarget; -import com.bumptech.glide.request.target.Target; -import com.bumptech.glide.request.transition.Transition; - -import java.util.HashMap; -import java.util.List; -import java.util.Objects; - -import app.fedilab.android.R; -import app.fedilab.android.activities.MainActivity; -import app.fedilab.android.client.API; -import app.fedilab.android.client.APIResponse; -import app.fedilab.android.client.Entities.Account; -import app.fedilab.android.client.Entities.Notification; -import app.fedilab.android.client.GNUAPI; -import app.fedilab.android.fragments.DisplayNotificationsFragment; -import app.fedilab.android.helper.Helper; -import app.fedilab.android.sqlite.AccountDAO; -import app.fedilab.android.sqlite.Sqlite; - -import static androidx.core.text.HtmlCompat.FROM_HTML_MODE_LEGACY; -import static app.fedilab.android.helper.Helper.getMainLogo; -import static app.fedilab.android.helper.Helper.getNotificationIcon; -import static app.fedilab.android.helper.Helper.sleeps; - - -/** - * Created by Thomas on 10/09/2019. - * Manage service for live notifications delayed - */ - -public class LiveNotificationDelayedService extends Service { - - - public static String CHANNEL_ID = "live_notifications"; - public static int totalAccount = 0; - public static int eventsCount = 0; - public static HashMap since_ids = new HashMap<>(); - protected Account account; - private NotificationChannel channel; - private boolean fetch; - - - public void onCreate() { - super.onCreate(); - final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - boolean notify = sharedpreferences.getBoolean(Helper.SET_NOTIFY, true); - - if (Build.VERSION.SDK_INT >= 26) { - channel = new NotificationChannel(CHANNEL_ID, - "Live notifications", - NotificationManager.IMPORTANCE_DEFAULT); - - ((NotificationManager) Objects.requireNonNull(getSystemService(Context.NOTIFICATION_SERVICE))).createNotificationChannel(channel); - } - - SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); - List accountStreams = new AccountDAO(LiveNotificationDelayedService.this, db).getAllAccountCrossAction(); - totalAccount = 0; - if (accountStreams != null) { - for (Account account : accountStreams) { - boolean allowStream = sharedpreferences.getBoolean(Helper.SET_ALLOW_STREAM + account.getId() + account.getInstance(), true); - if (allowStream) { - totalAccount++; - } - } - } - - if (Build.VERSION.SDK_INT >= 26) { - Intent myIntent = new Intent(LiveNotificationDelayedService.this, MainActivity.class); - PendingIntent pendingIntent = PendingIntent.getActivity( - LiveNotificationDelayedService.this, - 0, - myIntent, - PendingIntent.FLAG_UPDATE_CURRENT); - - android.app.Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID) - .setShowWhen(false) - .setContentIntent(pendingIntent) - .setContentTitle(getString(R.string.top_notification)) - .setSmallIcon(getNotificationIcon(LiveNotificationDelayedService.this)) - .setContentText(getString(R.string.top_notification_message, String.valueOf(totalAccount), String.valueOf(eventsCount))).build(); - if (notification != null) { - startForeground(1, notification); - } else { - return; - } - } - - - if (!notify) { - stopSelf(); - return; - } - if (totalAccount > 0) { - startStream(); - } else { - stopSelf(); - } - - } - - - @Override - public void onDestroy() { - super.onDestroy(); - for (Thread t : Thread.getAllStackTraces().keySet()) { - if (t.getName().startsWith("notif_delayed_")) { - t.interrupt(); - } - } - Thread.currentThread().interrupt(); - } - - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - boolean notify = sharedpreferences.getBoolean(Helper.SET_NOTIFY, true); - if (!notify || intent == null || intent.getBooleanExtra("stop", false)) { - totalAccount = 0; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - stopForeground(STOP_FOREGROUND_DETACH); - NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - assert notificationManager != null; - notificationManager.deleteNotificationChannel(CHANNEL_ID); - } - if (intent != null) { - intent.replaceExtras(new Bundle()); - } - stopSelf(); - } - if (totalAccount > 0) { - return START_STICKY; - } - return START_NOT_STICKY; - } - - - private void startStream() { - - SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); - if (Helper.liveNotifType(LiveNotificationDelayedService.this) == Helper.NOTIF_DELAYED) { - List accountStreams = new AccountDAO(LiveNotificationDelayedService.this, db).getAllAccountCrossAction(); - final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - fetch = true; - if (accountStreams != null) { - for (final Account accountStream : accountStreams) { - String key = accountStream.getUsername() + "@" + accountStream.getInstance(); - boolean allowStream = sharedpreferences.getBoolean(Helper.SET_ALLOW_STREAM + accountStream.getId() + accountStream.getInstance(), true); - if (!allowStream) { - continue; - } - if (!sleeps.containsKey(key)) { - sleeps.put(key, 30000); - } - Thread thread = Helper.getThreadByName("notif_delayed_" + key); - if (thread == null) { - startThread(accountStream, key); - } else if (thread.getState() != Thread.State.RUNNABLE) { - thread.interrupt(); - startThread(accountStream, key); - } - } - } - } - } - - private void startThread(Account accountStream, String key) { - Thread thread = new Thread() { - @Override - public void run() { - while (fetch) { - task(accountStream); - fetch = (Helper.liveNotifType(LiveNotificationDelayedService.this) == Helper.NOTIF_DELAYED); - if (sleeps.containsKey(key) && sleeps.get(key) != null) { - try { - Thread.sleep(sleeps.get(key)); - } catch (InterruptedException e) { - SystemClock.sleep(sleeps.get(key)); - } - } - } - } - }; - thread.setName("notif_delayed_" + key); - thread.start(); - } - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return null; - } - - - private void task(Account account) { - String key = account.getUsername() + "@" + account.getInstance(); - APIResponse apiResponse; - - String last_notifid = null; - if (since_ids.containsKey(key)) { - last_notifid = since_ids.get(key); - } - apiResponse = null; - try { - if (account.getSocial().compareTo("FRIENDICA") != 0 && account.getSocial().compareTo("GNU") != 0) { - API api; - api = new API(LiveNotificationDelayedService.this, account.getInstance(), account.getToken()); - apiResponse = api.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, last_notifid, false); - } else { - GNUAPI gnuApi; - gnuApi = new GNUAPI(LiveNotificationDelayedService.this, account.getInstance(), account.getToken()); - apiResponse = gnuApi.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, last_notifid); - } - } catch (Exception ignored) { - } - - if (apiResponse != null && apiResponse.getNotifications() != null && apiResponse.getNotifications().size() > 0) { - since_ids.put(key, apiResponse.getNotifications().get(0).getId()); - for (Notification notification : apiResponse.getNotifications()) { - if (last_notifid != null && notification.getId().compareTo(last_notifid) > 0) { - onRetrieveStreaming(account, notification); - sleeps.put(key, 30000); - } else { - if (apiResponse.getNotifications().size() == 1) { //TODO: use min id with Pixelfed when available for removing this fix. - if (sleeps.containsKey(key) && sleeps.get(key) != null) { - int newWaitTime = sleeps.get(key) + 30000; - if (newWaitTime > 900000) { - newWaitTime = 900000; - } - sleeps.put(key, newWaitTime); - } else { - sleeps.put(key, 60000); - } - } - break; - } - } - } else { - if (sleeps.containsKey(key) && sleeps.get(key) != null) { - int newWaitTime = sleeps.get(key) + 30000; - if (newWaitTime > 900000) { - newWaitTime = 900000; - } - sleeps.put(key, newWaitTime); - } else { - sleeps.put(key, 60000); - } - } - } - - - private void onRetrieveStreaming(Account account, Notification notification) { - - Bundle b = new Bundle(); - boolean canSendBroadCast = true; - Helper.EventStreaming event; - final SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); - try { - eventsCount++; - if (Build.VERSION.SDK_INT >= 26) { - channel = new NotificationChannel(CHANNEL_ID, - "Live notifications", - NotificationManager.IMPORTANCE_DEFAULT); - ((NotificationManager) Objects.requireNonNull(getSystemService(Context.NOTIFICATION_SERVICE))).createNotificationChannel(channel); - android.app.Notification notificationChannel = new NotificationCompat.Builder(this, CHANNEL_ID) - .setShowWhen(false) - .setContentTitle(getString(R.string.top_notification)) - .setSmallIcon(getNotificationIcon(LiveNotificationDelayedService.this)) - .setContentText(getString(R.string.top_notification_message, String.valueOf(totalAccount), String.valueOf(eventsCount))).build(); - - if (notificationChannel != null) { - startForeground(1, notificationChannel); - } else { - return; - } - } - - event = Helper.EventStreaming.NOTIFICATION; - boolean canNotify = Helper.canNotify(LiveNotificationDelayedService.this); - boolean notify = sharedpreferences.getBoolean(Helper.SET_NOTIFY, true); - String targeted_account = null; - Helper.NotifType notifType = Helper.NotifType.MENTION; - boolean activityRunning = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("isMainActivityRunning", false); - boolean allowStream = sharedpreferences.getBoolean(Helper.SET_ALLOW_STREAM + account.getId() + account.getInstance(), true); - if (!allowStream) { - canNotify = false; - } - if ((userId == null || !userId.equals(account.getId()) || !activityRunning) && canNotify && notify) { - boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true); - boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true); - boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true); - boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true); - boolean notif_status = sharedpreferences.getBoolean(Helper.SET_NOTIF_STATUS, true); - boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true); - boolean somethingToPush = (notif_follow || notif_add || notif_mention || notif_share || notif_poll || notif_status); - - String message = null; - if (somethingToPush) { - switch (notification.getType()) { - case "mention": - notifType = Helper.NotifType.MENTION; - if (notif_mention) { - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_mention)); - else - message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_mention)); - if (notification.getStatus() != null) { - if (notification.getStatus().getSpoiler_text() != null && notification.getStatus().getSpoiler_text().length() > 0) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text(), FROM_HTML_MODE_LEGACY)); - else - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text())); - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent(), FROM_HTML_MODE_LEGACY)); - else - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent())); - } - } - } else { - canSendBroadCast = false; - } - break; - case "status": - notifType = Helper.NotifType.STATUS; - if (notif_status) { - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_status)); - else - message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_status)); - if (notification.getStatus() != null) { - if (notification.getStatus().getSpoiler_text() != null && notification.getStatus().getSpoiler_text().length() > 0) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text(), FROM_HTML_MODE_LEGACY)); - else - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text())); - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent(), FROM_HTML_MODE_LEGACY)); - else - message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent())); - } - } - } else { - canSendBroadCast = false; - } - break; - case "reblog": - notifType = Helper.NotifType.BOOST; - if (notif_share) { - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_reblog)); - else - message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_reblog)); - } else { - canSendBroadCast = false; - } - break; - case "favourite": - notifType = Helper.NotifType.FAV; - if (notif_add) { - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_favourite)); - else - message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_favourite)); - } else { - canSendBroadCast = false; - } - break; - case "follow_request": - notifType = Helper.NotifType.FOLLLOW; - if (notif_follow) { - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_follow_request)); - else - message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_follow_request)); - targeted_account = notification.getAccount().getId(); - } else { - canSendBroadCast = false; - } - break; - case "follow": - notifType = Helper.NotifType.FOLLLOW; - if (notif_follow) { - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - message = String.format("%s %s", notification.getAccount().getDisplay_name(), getString(R.string.notif_follow)); - else - message = String.format("@%s %s", notification.getAccount().getAcct(), getString(R.string.notif_follow)); - targeted_account = notification.getAccount().getId(); - } else { - canSendBroadCast = false; - } - break; - case "poll": - notifType = Helper.NotifType.POLL; - if (notif_poll) { - if (notification.getAccount().getId() != null && notification.getAccount().getId().equals(userId)) - message = getString(R.string.notif_poll_self); - else - message = getString(R.string.notif_poll); - } else { - canSendBroadCast = false; - } - break; - default: - } - - //Some others notification - final Intent intent = new Intent(LiveNotificationDelayedService.this, MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(Helper.INTENT_ACTION, Helper.NOTIFICATION_INTENT); - intent.putExtra(Helper.PREF_KEY_ID, account.getId()); - intent.putExtra(Helper.PREF_INSTANCE, account.getInstance()); - if (targeted_account != null) { - intent.putExtra(Helper.INTENT_TARGETED_ACCOUNT, targeted_account); - } - final String finalMessage = message; - Handler mainHandler = new Handler(Looper.getMainLooper()); - Helper.NotifType finalNotifType = notifType; - Runnable myRunnable = () -> { - if (finalMessage != null) { - Glide.with(LiveNotificationDelayedService.this) - .asBitmap() - .load(notification.getAccount().getAvatar()) - .listener(new RequestListener() { - @Override - public boolean onResourceReady(Bitmap resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { - return false; - } - - @Override - public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { - Helper.notify_user(LiveNotificationDelayedService.this, account, intent, BitmapFactory.decodeResource(getResources(), - getMainLogo(LiveNotificationDelayedService.this)), finalNotifType, "@" + notification.getAccount().getAcct(), finalMessage); - return false; - } - }) - .into(new CustomTarget() { - @Override - public void onResourceReady(@NonNull Bitmap resource, Transition transition) { - - Helper.notify_user(LiveNotificationDelayedService.this, account, intent, resource, finalNotifType, "@" + notification.getAccount().getAcct(), finalMessage); - } - - @Override - public void onLoadCleared(@Nullable Drawable placeholder) { - - } - }); - } - }; - mainHandler.post(myRunnable); - } - } - - if (canSendBroadCast) { - b.putString("userIdService", account.getId()); - Intent intentBC = new Intent(Helper.RECEIVE_DATA); - intentBC.putExtra("eventStreaming", event); - intentBC.putExtras(b); - b.putParcelable("data", notification); - LocalBroadcastManager.getInstance(LiveNotificationDelayedService.this).sendBroadcast(intentBC); - SharedPreferences.Editor editor = sharedpreferences.edit(); - editor.putString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), notification.getId()); - editor.apply(); - } - } catch (Exception ignored) { - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/services/RestartLiveNotificationReceiver.java b/app/src/main/java/app/fedilab/android/services/RestartLiveNotificationReceiver.java deleted file mode 100644 index 5ff6fb569..000000000 --- a/app/src/main/java/app/fedilab/android/services/RestartLiveNotificationReceiver.java +++ /dev/null @@ -1,45 +0,0 @@ -package app.fedilab.android.services; -/* Copyright 2017 Thomas Schneider - * - * This file is a part of Fedilab - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Fedilab; if not, - * see . */ - -import android.annotation.SuppressLint; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -import app.fedilab.android.helper.Helper; -import app.fedilab.android.jobs.NotificationsSyncJob; - -import static app.fedilab.android.helper.BaseHelper.startStreaming; - -/** - * Created by Thomas on 22/09/2017. - * BroadcastReceiver for restarting the service - */ - -public class RestartLiveNotificationReceiver extends BroadcastReceiver { - - - @SuppressLint("UnsafeProtectedBroadcastReceiver") - @Override - public void onReceive(Context context, Intent intent) { - int type = Helper.liveNotifType(context); - if (type == Helper.NOTIF_DELAYED || type == Helper.NOTIF_LIVE) { - startStreaming(context); - } else { - NotificationsSyncJob.schedule(false); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/services/StopDelayedNotificationReceiver.java b/app/src/main/java/app/fedilab/android/services/StopDelayedNotificationReceiver.java deleted file mode 100644 index 2e1e6329f..000000000 --- a/app/src/main/java/app/fedilab/android/services/StopDelayedNotificationReceiver.java +++ /dev/null @@ -1,41 +0,0 @@ -package app.fedilab.android.services; -/* Copyright 2019 Thomas Schneider - * - * This file is a part of Fedilab - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Fedilab; if not, - * see . */ - -import android.annotation.SuppressLint; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - - -/** - * Created by Thomas on 18/10/2019. - * BroadcastReceiver for restarting delayed notification service - */ - -public class StopDelayedNotificationReceiver extends BroadcastReceiver { - - @SuppressLint("UnsafeProtectedBroadcastReceiver") - @Override - public void onReceive(Context context, Intent intent) { - Intent streamingServiceIntent = new Intent(context, LiveNotificationDelayedService.class); - streamingServiceIntent.putExtra("stop", true); - try { - context.startService(streamingServiceIntent); - } catch (Exception ignored) { - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/services/StopLiveNotificationReceiver.java b/app/src/main/java/app/fedilab/android/services/StopLiveNotificationReceiver.java deleted file mode 100644 index 7c4b924e8..000000000 --- a/app/src/main/java/app/fedilab/android/services/StopLiveNotificationReceiver.java +++ /dev/null @@ -1,41 +0,0 @@ -package app.fedilab.android.services; -/* Copyright 2017 Thomas Schneider - * - * This file is a part of Fedilab - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Fedilab; if not, - * see . */ - -import android.annotation.SuppressLint; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - - -/** - * Created by Thomas on 22/09/2017. - * BroadcastReceiver for restarting the service - */ - -public class StopLiveNotificationReceiver extends BroadcastReceiver { - - @SuppressLint("UnsafeProtectedBroadcastReceiver") - @Override - public void onReceive(Context context, Intent intent) { - Intent streamingServiceIntent = new Intent(context, LiveNotificationService.class); - streamingServiceIntent.putExtra("stop", true); - try { - context.startService(streamingServiceIntent); - } catch (Exception ignored) { - } - } - -} \ No newline at end of file From e1e6de809d7d4a0c4e646bfb2909a9acac8c71b4 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 28 Feb 2021 10:58:17 +0100 Subject: [PATCH 18/35] copy release notes --- .../services/LiveNotificationService.java | 23 ------------------- 1 file changed, 23 deletions(-) delete mode 100644 app/src/common/java/app/fedilab/android/services/LiveNotificationService.java diff --git a/app/src/common/java/app/fedilab/android/services/LiveNotificationService.java b/app/src/common/java/app/fedilab/android/services/LiveNotificationService.java deleted file mode 100644 index 1e4bf19c3..000000000 --- a/app/src/common/java/app/fedilab/android/services/LiveNotificationService.java +++ /dev/null @@ -1,23 +0,0 @@ -package app.fedilab.android.services; -/* Copyright 2020 Thomas Schneider - * - * This file is a part of Fedilab - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Fedilab; if not, - * see . */ - -import app.fedilab.android.helper.Helper; - -public class LiveNotificationService extends BaseLiveNotificationService { - static { - Helper.installProvider(); - } -} From 2eb4c8d8f5aadbc7f319b4b0088ed505309fb9dd Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 28 Feb 2021 11:21:07 +0100 Subject: [PATCH 19/35] copy release notes --- .../android/services/UpgradeReceiver.java | 42 ------------------- 1 file changed, 42 deletions(-) delete mode 100644 app/src/main/java/app/fedilab/android/services/UpgradeReceiver.java diff --git a/app/src/main/java/app/fedilab/android/services/UpgradeReceiver.java b/app/src/main/java/app/fedilab/android/services/UpgradeReceiver.java deleted file mode 100644 index 98bd9f980..000000000 --- a/app/src/main/java/app/fedilab/android/services/UpgradeReceiver.java +++ /dev/null @@ -1,42 +0,0 @@ -package app.fedilab.android.services; -/* Copyright 2020 Thomas Schneider - * - * This file is a part of Fedilab - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Fedilab; if not, - * see . */ - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - - -public class UpgradeReceiver extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - - if (intent.getAction() != null && intent.getAction().compareTo(Intent.ACTION_MY_PACKAGE_REPLACED) == 0) { - Intent streamingServiceIntent = new Intent(context, LiveNotificationDelayedService.class); - streamingServiceIntent.putExtra("stop", true); - try { - context.startService(streamingServiceIntent); - } catch (Exception ignored) { - } - streamingServiceIntent = new Intent(context, LiveNotificationService.class); - streamingServiceIntent.putExtra("stop", true); - try { - context.startService(streamingServiceIntent); - } catch (Exception ignored) { - } - } - } -} \ No newline at end of file From 3e4cc1cc7df293dbef352359640281c398b73dd8 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 28 Feb 2021 11:46:34 +0100 Subject: [PATCH 20/35] working push --- app/build.gradle | 2 +- app/src/main/AndroidManifest.xml | 28 +- .../android/activities/BaseMainActivity.java | 55 ++-- .../java/app/fedilab/android/client/API.java | 1 - .../android/client/HttpsConnection.java | 2 - .../android/drawers/AccountLiveAdapter.java | 7 - .../fragments/ContentSettingsFragment.java | 102 +----- .../DisplayNotificationsFragment.java | 21 +- .../fragments/DisplayStatusFragment.java | 7 +- .../fedilab/android/helper/BaseHelper.java | 63 ++-- .../java/app/fedilab/android/helper/ECDH.java | 6 - .../android/helper/NotificationsHelper.java | 296 +++++++++++++++++- .../jobs/BaseNotificationsSyncJob.java | 210 +------------ .../android/services/UnifiedPushService.java | 41 ++- .../fragment_settings_notifications.xml | 96 ------ app/src/main/res/values/strings.xml | 6 +- 16 files changed, 401 insertions(+), 542 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 962c6db73..47c462ded 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -177,7 +177,7 @@ dependencies { implementation "com.madgag.spongycastle:bctls-jdk15on:1.58.0.0" //implementation 'org.bouncycastle:bcprov-jdk15on:1.64' - implementation 'com.github.UnifiedPush:android-connector:dev-SNAPSHOT' + implementation 'com.github.UnifiedPush:android-connector:1.0.0' //Flavors //Playstore diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1ae852253..91e1fe031 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -69,18 +69,7 @@ android:name="app.fedilab.android.services.BackupNotificationInDataBaseService" android:exported="false" /> - - - - - - - - - - + @@ -88,20 +77,7 @@ - - - - - - - - - - + diff --git a/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java index 313ec321c..b4583a29f 100644 --- a/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java @@ -76,7 +76,6 @@ import com.google.android.material.navigation.NavigationView; import com.google.android.material.tabs.TabLayout; import org.jetbrains.annotations.NotNull; -import org.unifiedpush.android.connector.Registration; import java.io.BufferedReader; import java.io.File; @@ -96,7 +95,6 @@ import java.util.regex.Matcher; import app.fedilab.android.BuildConfig; import app.fedilab.android.R; import app.fedilab.android.asynctasks.ManageFiltersAsyncTask; -import app.fedilab.android.asynctasks.PostSubscriptionAsyncTask; import app.fedilab.android.asynctasks.RetrieveAccountsAsyncTask; import app.fedilab.android.asynctasks.RetrieveFeedsAsyncTask; import app.fedilab.android.asynctasks.RetrieveInstanceAsyncTask; @@ -158,7 +156,8 @@ import es.dmoral.toasty.Toasty; import static app.fedilab.android.activities.WebviewActivity.trackingDomains; import static app.fedilab.android.asynctasks.ManageFiltersAsyncTask.action.GET_ALL_FILTER; -import static app.fedilab.android.helper.BaseHelper.startStreaming; +import static app.fedilab.android.helper.BaseHelper.NOTIF_NONE; +import static app.fedilab.android.helper.BaseHelper.NOTIF_PUSH; import static app.fedilab.android.helper.Helper.changeDrawableColor; @@ -422,9 +421,7 @@ public abstract class BaseMainActivity extends BaseActivity main_app_container = findViewById(R.id.main_app_container); - if (social == UpdateAccountInfoAsyncTask.SOCIAL.MASTODON || social == UpdateAccountInfoAsyncTask.SOCIAL.PLEROMA) { - new Registration().registerAppWithDialog(BaseMainActivity.this, account.getUsername() + "@" + account.getInstance()); - } + if (social == UpdateAccountInfoAsyncTask.SOCIAL.MASTODON || social == UpdateAccountInfoAsyncTask.SOCIAL.PLEROMA || social == UpdateAccountInfoAsyncTask.SOCIAL.GNU || social == UpdateAccountInfoAsyncTask.SOCIAL.FRIENDICA) { new SyncTimelinesAsyncTask(BaseMainActivity.this, 0, Helper.canFetchList(BaseMainActivity.this, account), BaseMainActivity.this); @@ -1201,55 +1198,39 @@ public abstract class BaseMainActivity extends BaseActivity //Live notification mode final Spinner set_live_type = dialogView.findViewById(R.id.set_live_type); - String[] labels = {getString(R.string.live_notif), getString(R.string.live_delayed), getString(R.string.no_live_notif)}; + String[] labels = {getString(R.string.push_notif), getString(R.string.no_live_notif)}; ArrayAdapter adapterLive = new ArrayAdapter<>(BaseMainActivity.this, android.R.layout.simple_spinner_dropdown_item, labels); set_live_type.setAdapter(adapterLive); TextView set_live_type_indication = dialogView.findViewById(R.id.set_live_type_indication); switch (Helper.liveNotifType(BaseMainActivity.this)) { - case Helper.NOTIF_LIVE: - set_live_type_indication.setText(R.string.live_notif_indication); - break; - case Helper.NOTIF_DELAYED: - set_live_type_indication.setText(R.string.set_live_type_indication); + case Helper.NOTIF_PUSH: + set_live_type_indication.setText(R.string.set_push_notifications); break; case Helper.NOTIF_NONE: set_live_type_indication.setText(R.string.no_live_indication); break; } - set_live_type.setSelection(Helper.liveNotifType(BaseMainActivity.this), false); + int livenotif = Helper.liveNotifType(BaseMainActivity.this); + int selection = 0; + if (livenotif == NOTIF_NONE) + selection = 1; + set_live_type.setSelection(selection, false); set_live_type.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { SharedPreferences.Editor editor = sharedpreferences.edit(); switch (position) { - case Helper.NOTIF_LIVE: - editor.putBoolean(Helper.SET_LIVE_NOTIFICATIONS, true); - editor.putBoolean(Helper.SET_DELAYED_NOTIFICATIONS, false); + case 0: + editor.putBoolean(Helper.SET_PUSH_NOTIFICATIONS, true); editor.apply(); - startStreaming(BaseMainActivity.this); + Helper.startStreaming(BaseMainActivity.this); + set_live_type_indication.setText(R.string.set_push_notifications); break; - case Helper.NOTIF_DELAYED: - editor.putBoolean(Helper.SET_LIVE_NOTIFICATIONS, false); - editor.putBoolean(Helper.SET_DELAYED_NOTIFICATIONS, true); - editor.apply(); - startStreaming(BaseMainActivity.this); - break; - case Helper.NOTIF_NONE: - editor.putBoolean(Helper.SET_LIVE_NOTIFICATIONS, false); - editor.putBoolean(Helper.SET_DELAYED_NOTIFICATIONS, false); - editor.apply(); - break; - } - switch (Helper.liveNotifType(BaseMainActivity.this)) { - case Helper.NOTIF_LIVE: - set_live_type_indication.setText(R.string.live_notif_indication); - break; - case Helper.NOTIF_DELAYED: - set_live_type_indication.setText(R.string.set_live_type_indication); - break; - case Helper.NOTIF_NONE: + case 1: + editor.putBoolean(Helper.SET_PUSH_NOTIFICATIONS, false); set_live_type_indication.setText(R.string.no_live_indication); + editor.apply(); break; } } diff --git a/app/src/main/java/app/fedilab/android/client/API.java b/app/src/main/java/app/fedilab/android/client/API.java index 1e10afbc7..632271be6 100644 --- a/app/src/main/java/app/fedilab/android/client/API.java +++ b/app/src/main/java/app/fedilab/android/client/API.java @@ -23,7 +23,6 @@ import android.os.Bundle; import android.text.Html; import android.text.SpannableString; import android.util.Base64; -import android.util.Log; import androidx.localbroadcastmanager.content.LocalBroadcastManager; diff --git a/app/src/main/java/app/fedilab/android/client/HttpsConnection.java b/app/src/main/java/app/fedilab/android/client/HttpsConnection.java index a82e76cd3..0b71fc085 100644 --- a/app/src/main/java/app/fedilab/android/client/HttpsConnection.java +++ b/app/src/main/java/app/fedilab/android/client/HttpsConnection.java @@ -19,7 +19,6 @@ import android.content.SharedPreferences; import android.os.Build; import android.text.Html; import android.text.SpannableString; -import android.util.Log; import com.google.gson.JsonObject; @@ -369,7 +368,6 @@ public class HttpsConnection { postData.append('='); postData.append(param.getValue()); } - Log.v(Helper.TAG, "postData: " + postData); byte[] postDataBytes = postData.toString().getBytes(StandardCharsets.UTF_8); if (proxy != null) httpURLConnection = (HttpsURLConnection) url.openConnection(proxy); diff --git a/app/src/main/java/app/fedilab/android/drawers/AccountLiveAdapter.java b/app/src/main/java/app/fedilab/android/drawers/AccountLiveAdapter.java index fcee4070d..831ddf755 100644 --- a/app/src/main/java/app/fedilab/android/drawers/AccountLiveAdapter.java +++ b/app/src/main/java/app/fedilab/android/drawers/AccountLiveAdapter.java @@ -31,7 +31,6 @@ import java.util.List; import app.fedilab.android.R; import app.fedilab.android.client.Entities.Account; import app.fedilab.android.helper.Helper; -import app.fedilab.android.services.LiveNotificationDelayedService; import static android.content.Context.MODE_PRIVATE; @@ -72,12 +71,6 @@ public class AccountLiveAdapter extends RecyclerView.Adapter= Build.VERSION_CODES.O) { set_notif_sound.setVisibility(View.GONE); channels_container.setVisibility(View.VISIBLE); - set_hide_status_bar_container.setVisibility(View.VISIBLE); - set_hide_status_bar.setOnClickListener(v -> { - Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS); - intent.putExtra(Settings.EXTRA_APP_PACKAGE, context.getPackageName()); - intent.putExtra(Settings.EXTRA_CHANNEL_ID, LiveNotificationDelayedService.CHANNEL_ID); - startActivity(intent); - }); - sound_boost.setOnClickListener(v -> { Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS); @@ -923,36 +905,6 @@ public class ContentSettingsFragment extends Fragment implements OnRetrieveRemot } } - boolean allow_live_notifications = sharedpreferences.getBoolean(Helper.SET_ALLOW_STREAM + userId + instance, true); - TextView set_allow_live_notifications_title = rootView.findViewById(R.id.set_allow_live_notifications_title); - if (account != null) { - set_allow_live_notifications_title.setText(context.getString(R.string.set_allow_live_notifications, account.getAcct() + "@" + account.getInstance())); - } - final SwitchCompat set_allow_live_notifications = rootView.findViewById(R.id.set_allow_live_notifications); - set_allow_live_notifications.setChecked(allow_live_notifications); - set_allow_live_notifications.setOnClickListener(v -> { - SharedPreferences.Editor editor = sharedpreferences.edit(); - editor.putBoolean(Helper.SET_ALLOW_STREAM + userId + instance, set_allow_live_notifications.isChecked()); - editor.apply(); - if (set_allow_live_notifications.isChecked()) { - LiveNotificationDelayedService.totalAccount++; - } else { - LiveNotificationDelayedService.totalAccount--; - } - if (set_allow_live_notifications.isChecked()) { - LiveNotificationDelayedService.totalAccount++; - } else { - LiveNotificationDelayedService.totalAccount--; - } - Helper.startStreaming(context); - - }); - - final ImageButton set_allow_live_notifications_others = rootView.findViewById(R.id.set_allow_live_notifications_others); - set_allow_live_notifications_others.setOnClickListener(view -> { - Intent intent = new Intent(context, LiveNotificationSettingsAccountsActivity.class); - startActivity(intent); - }); boolean notify = sharedpreferences.getBoolean(Helper.SET_NOTIFY, true); @@ -971,35 +923,22 @@ public class ContentSettingsFragment extends Fragment implements OnRetrieveRemot editor.apply(); if (isChecked) { notification_container.setVisibility(View.VISIBLE); - notification_container.setVisibility(View.VISIBLE); - Helper.startStreaming(context); } else { - context.sendBroadcast(new Intent(context, StopLiveNotificationReceiver.class)); - context.sendBroadcast(new Intent(context, StopDelayedNotificationReceiver.class)); - ApplicationJob.cancelAllJob(NotificationsSyncJob.NOTIFICATION_REFRESH); notification_container.setVisibility(View.GONE); } + Helper.startStreaming(context); }); //Live notification mode final Spinner set_live_type = rootView.findViewById(R.id.set_live_type); - String[] labels = {context.getString(R.string.live_notif), context.getString(R.string.live_delayed), context.getString(R.string.no_live_notif)}; - ArrayAdapter adapterLive = new ArrayAdapter<>(Objects.requireNonNull(getActivity()), - android.R.layout.simple_spinner_dropdown_item, labels); + String[] labels = {context.getString(R.string.push_notif), context.getString(R.string.no_live_notif)}; + - LinearLayout live_notif_per_account = rootView.findViewById(R.id.live_notif_per_account); - set_live_type.setAdapter(adapterLive); - if (Helper.liveNotifType(context) == Helper.NOTIF_NONE) { - live_notif_per_account.setVisibility(View.GONE); - } TextView set_live_type_indication = rootView.findViewById(R.id.set_live_type_indication); switch (Helper.liveNotifType(context)) { - case Helper.NOTIF_LIVE: - set_live_type_indication.setText(R.string.live_notif_indication); - break; - case Helper.NOTIF_DELAYED: - set_live_type_indication.setText(R.string.set_live_type_indication); + case Helper.NOTIF_PUSH: + set_live_type_indication.setText(R.string.set_push_notifications); break; case Helper.NOTIF_NONE: set_live_type_indication.setText(R.string.no_live_indication); @@ -1008,40 +947,25 @@ public class ContentSettingsFragment extends Fragment implements OnRetrieveRemot set_live_type.setSelection(Helper.liveNotifType(context)); liveNotificationCount = 0; set_live_type.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @SuppressLint("ApplySharedPref") @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { if (liveNotificationCount > 0) { SharedPreferences.Editor editor = sharedpreferences.edit(); - - context.sendBroadcast(new Intent(context, StopLiveNotificationReceiver.class)); - context.sendBroadcast(new Intent(context, StopDelayedNotificationReceiver.class)); - ApplicationJob.cancelAllJob(NotificationsSyncJob.NOTIFICATION_REFRESH); switch (position) { - case Helper.NOTIF_LIVE: - editor.putBoolean(Helper.SET_LIVE_NOTIFICATIONS, true); - editor.putBoolean(Helper.SET_DELAYED_NOTIFICATIONS, false); - live_notif_per_account.setVisibility(View.VISIBLE); + case Helper.NOTIF_PUSH: + editor.putBoolean(Helper.SET_PUSH_NOTIFICATIONS, true); editor.commit(); - set_live_type_indication.setText(R.string.live_notif_indication); - Helper.startStreaming(context); - break; - case Helper.NOTIF_DELAYED: - editor.putBoolean(Helper.SET_LIVE_NOTIFICATIONS, false); - editor.putBoolean(Helper.SET_DELAYED_NOTIFICATIONS, true); - live_notif_per_account.setVisibility(View.VISIBLE); - set_live_type_indication.setText(R.string.set_live_type_indication); - editor.commit(); - Helper.startStreaming(context); + set_live_type_indication.setText(R.string.set_push_notifications); + break; case Helper.NOTIF_NONE: - editor.putBoolean(Helper.SET_LIVE_NOTIFICATIONS, false); - editor.putBoolean(Helper.SET_DELAYED_NOTIFICATIONS, false); + editor.putBoolean(Helper.SET_PUSH_NOTIFICATIONS, false); editor.commit(); set_live_type_indication.setText(R.string.no_live_indication); - live_notif_per_account.setVisibility(View.GONE); - NotificationsSyncJob.schedule(false); break; } + Helper.startStreaming(context); } else { liveNotificationCount++; } diff --git a/app/src/main/java/app/fedilab/android/fragments/DisplayNotificationsFragment.java b/app/src/main/java/app/fedilab/android/fragments/DisplayNotificationsFragment.java index ff72e13b1..726b2543f 100644 --- a/app/src/main/java/app/fedilab/android/fragments/DisplayNotificationsFragment.java +++ b/app/src/main/java/app/fedilab/android/fragments/DisplayNotificationsFragment.java @@ -20,7 +20,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; -import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.os.Handler; import android.os.Looper; @@ -55,12 +54,10 @@ import app.fedilab.android.drawers.NotificationsListAdapter; import app.fedilab.android.helper.Helper; import app.fedilab.android.interfaces.OnRetrieveMissingNotificationsInterface; import app.fedilab.android.interfaces.OnRetrieveNotificationsInterface; -import app.fedilab.android.services.LiveNotificationDelayedService; -import app.fedilab.android.sqlite.AccountDAO; -import app.fedilab.android.sqlite.Sqlite; + import es.dmoral.toasty.Toasty; -import static android.content.Context.MODE_PRIVATE; + import static app.fedilab.android.activities.BaseMainActivity.countNewNotifications; @@ -314,12 +311,6 @@ public class DisplayNotificationsFragment extends Fragment implements OnRetrieve if (type == Type.ALL) { if (lastReadNotifications != null && notifications.get(0).getId().compareTo(lastReadNotifications) > 0) { countNewNotifications++; - SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); - String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); - String instance = sharedpreferences.getString(Helper.PREF_INSTANCE, Helper.getLiveInstance(context)); - SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); - Account accountdb = new AccountDAO(context, db).getUniqAccount(userId, instance); - LiveNotificationDelayedService.since_ids.put(accountdb.getAcct() + "@" + accountdb.getInstance(), notifications.get(0).getId()); } } for (Notification tmpNotification : notifications) { @@ -446,14 +437,6 @@ public class DisplayNotificationsFragment extends Fragment implements OnRetrieve MainActivity.lastNotificationId = notifications.get(0).getId(); updateNotificationLastId(notifications.get(0).getId()); } - SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); - String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); - String instance = sharedpreferences.getString(Helper.PREF_INSTANCE, Helper.getLiveInstance(context)); - SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); - Account account = new AccountDAO(context, db).getUniqAccount(userId, instance); - if (MainActivity.lastNotificationId != null && notifications.get(0).getId().compareTo(MainActivity.lastNotificationId) > 0) { - LiveNotificationDelayedService.since_ids.put(account.getAcct() + "@" + account.getInstance(), notifications.get(0).getId()); - } } if (textviewNoAction.getVisibility() == View.VISIBLE) { textviewNoAction.setVisibility(View.GONE); diff --git a/app/src/main/java/app/fedilab/android/fragments/DisplayStatusFragment.java b/app/src/main/java/app/fedilab/android/fragments/DisplayStatusFragment.java index 6b21b7c88..41285d734 100644 --- a/app/src/main/java/app/fedilab/android/fragments/DisplayStatusFragment.java +++ b/app/src/main/java/app/fedilab/android/fragments/DisplayStatusFragment.java @@ -96,6 +96,9 @@ import app.fedilab.android.sqlite.SearchDAO; import app.fedilab.android.sqlite.Sqlite; import es.dmoral.toasty.Toasty; +import static app.fedilab.android.helper.BaseHelper.NOTIF_PUSH; +import static app.fedilab.android.helper.BaseHelper.liveNotifType; + /** * Created by Thomas on 24/04/2017. @@ -756,7 +759,7 @@ public class DisplayStatusFragment extends Fragment implements OnPostActionInter public void onResume() { super.onResume(); swipeRefreshLayout.setEnabled(true); - boolean liveNotifications = sharedpreferences.getBoolean(Helper.SET_LIVE_NOTIFICATIONS, true); + boolean liveNotifications = liveNotifType(context) == NOTIF_PUSH; if (type == RetrieveFeedsAsyncTask.Type.HOME || type == RetrieveFeedsAsyncTask.Type.PF_HOME) { if (this.isVisible()) { if (statuses != null && statuses.size() > 0) { @@ -861,7 +864,7 @@ public class DisplayStatusFragment extends Fragment implements OnPostActionInter super.setMenuVisibility(visible); if (context == null) return; - int liveNotifications = Helper.liveNotifType(context); + int liveNotifications = liveNotifType(context); //Store last toot id for home timeline to avoid to notify for those that have been already seen if (type == RetrieveFeedsAsyncTask.Type.HOME || type == RetrieveFeedsAsyncTask.Type.PF_HOME) { if (visible) { diff --git a/app/src/main/java/app/fedilab/android/helper/BaseHelper.java b/app/src/main/java/app/fedilab/android/helper/BaseHelper.java index d1ec08ed5..7f0104f0d 100644 --- a/app/src/main/java/app/fedilab/android/helper/BaseHelper.java +++ b/app/src/main/java/app/fedilab/android/helper/BaseHelper.java @@ -137,6 +137,7 @@ import net.gotev.uploadservice.UploadService; import net.gotev.uploadservice.okhttp.OkHttpStack; import org.jetbrains.annotations.NotNull; +import org.unifiedpush.android.connector.Registration; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -211,8 +212,8 @@ import app.fedilab.android.client.Entities.Status; import app.fedilab.android.client.Entities.Tag; import app.fedilab.android.client.Entities.TagTimeline; import app.fedilab.android.client.Tls12SocketFactory; -import app.fedilab.android.services.LiveNotificationDelayedService; -import app.fedilab.android.services.LiveNotificationService; +import app.fedilab.android.jobs.ApplicationJob; +import app.fedilab.android.jobs.NotificationsSyncJob; import app.fedilab.android.sqlite.AccountDAO; import app.fedilab.android.sqlite.MainMenuDAO; import app.fedilab.android.sqlite.Sqlite; @@ -336,7 +337,7 @@ public class BaseHelper { public static final String SET_TIME_FROM = "set_time_from"; public static final String SET_TIME_TO = "set_time_to"; public static final String SET_AUTO_STORE = "set_auto_store"; - public static final String SET_POPUP_PUSH = "set_popup_push_new"; + public static final String SET_POPUP_PUSH = "set_popup_push_new_push"; public static final String SET_POPUP_RELEASE_NOTES = "set_popup_push_release_notes"; public static final String SET_NSFW_TIMEOUT = "set_nsfw_timeout"; public static final String SET_MED_DESC_TIMEOUT = "set_med_desc_timeout"; @@ -348,8 +349,7 @@ public class BaseHelper { public static final String SET_SHOW_BOOSTS = "set_show_boost"; public static final String SET_SHOW_REPLIES = "set_show_replies"; public static final String SET_VIDEO_NSFW = "set_video_nsfw"; - public static final String SET_LIVE_NOTIFICATIONS = "set_live_notifications"; - public static final String SET_DELAYED_NOTIFICATIONS = "set_delayed_notifications"; + public static final String SET_PUSH_NOTIFICATIONS = "set_push_notifications"; public static final String SET_DISABLE_GIF = "set_disable_gif"; public static final String SET_DISABLE_ANIMATED_EMOJI = "set_disable_animated_emoji"; public static final String SET_CAPITALIZE = "set_capitalize"; @@ -406,8 +406,7 @@ public class BaseHelper { public static final int THEME_DARK = 2; public static final int THEME_BLACK = 3; - public static final int NOTIF_LIVE = 0; - public static final int NOTIF_DELAYED = 1; + public static final int NOTIF_PUSH = 3; public static final int NOTIF_NONE = 2; public static final int LED_COLOUR = 0; @@ -589,17 +588,32 @@ public class BaseHelper { public static int liveNotifType(Context context) { SharedPreferences sharedpreferences = context.getSharedPreferences(APP_PREFS, Context.MODE_PRIVATE); - boolean live = sharedpreferences.getBoolean(SET_LIVE_NOTIFICATIONS, false); - boolean delayed = sharedpreferences.getBoolean(SET_DELAYED_NOTIFICATIONS, true); - if (delayed) { - return NOTIF_DELAYED; - } else if (live) { - return NOTIF_LIVE; + boolean push = sharedpreferences.getBoolean(SET_PUSH_NOTIFICATIONS, false); + if (push) { + return NOTIF_PUSH; } else { return NOTIF_NONE; } } + public static void startStreaming(Context context) { + int liveNotifications = liveNotifType(context); + switch (liveNotifications) { + case NOTIF_PUSH: + if (social == UpdateAccountInfoAsyncTask.SOCIAL.MASTODON || social == UpdateAccountInfoAsyncTask.SOCIAL.PLEROMA) { + new Registration().registerAppWithDialog(context); + ApplicationJob.cancelAllJob(NotificationsSyncJob.NOTIFICATION_REFRESH); + } else { + new Registration().unregisterApp(context); + NotificationsSyncJob.schedule(false); + } + break; + case NOTIF_NONE: + new Registration().unregisterApp(context); + NotificationsSyncJob.schedule(false); + break; + } + } /*** * Check if the user is connected to Internet @@ -4331,29 +4345,6 @@ public class BaseHelper { } - public static void startStreaming(Context context) { - int liveNotifications = liveNotifType(context); - Intent streamingIntent = null; - switch (liveNotifications) { - case NOTIF_LIVE: - streamingIntent = new Intent(context, LiveNotificationService.class); - break; - case NOTIF_DELAYED: - streamingIntent = new Intent(context, LiveNotificationDelayedService.class); - break; - } - if (streamingIntent != null) { - try { - if (Build.VERSION.SDK_INT >= 26) { - context.startForegroundService(streamingIntent); - } else { - context.startService(streamingIntent); - } - } catch (Exception ignored) { - context.startService(streamingIntent); - } - } - } public static boolean canFetchList(Context context, Account account) { SharedPreferences sharedpreferences = context.getSharedPreferences(APP_PREFS, Context.MODE_PRIVATE); diff --git a/app/src/main/java/app/fedilab/android/helper/ECDH.java b/app/src/main/java/app/fedilab/android/helper/ECDH.java index e60173d51..582e76e5e 100644 --- a/app/src/main/java/app/fedilab/android/helper/ECDH.java +++ b/app/src/main/java/app/fedilab/android/helper/ECDH.java @@ -18,7 +18,6 @@ import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.util.Base64; -import android.util.Log; import androidx.preference.PreferenceManager; @@ -115,11 +114,6 @@ public class ECDH { private static byte[] generateSecret(PrivateKey myPrivKey, PublicKey otherPubKey) throws Exception { ECPublicKey ecPubKey = (ECPublicKey) otherPubKey; - Log.d(TAG, "public key Wx: " - + ecPubKey.getW().getAffineX().toString(16)); - Log.d(TAG, "public key Wy: " - + ecPubKey.getW().getAffineY().toString(16)); - KeyAgreement keyAgreement = KeyAgreement.getInstance(KEGEN_ALG, PROVIDER); keyAgreement.init(myPrivKey); keyAgreement.doPhase(otherPubKey, true); diff --git a/app/src/main/java/app/fedilab/android/helper/NotificationsHelper.java b/app/src/main/java/app/fedilab/android/helper/NotificationsHelper.java index 628933454..61f77db2c 100644 --- a/app/src/main/java/app/fedilab/android/helper/NotificationsHelper.java +++ b/app/src/main/java/app/fedilab/android/helper/NotificationsHelper.java @@ -1,4 +1,298 @@ package app.fedilab.android.helper; +/* Copyright 2021 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ -class NotificationsHelper { +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.Drawable; +import android.os.Handler; +import android.os.Looper; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.bumptech.glide.Glide; +import com.bumptech.glide.load.DataSource; +import com.bumptech.glide.load.engine.GlideException; +import com.bumptech.glide.request.RequestListener; +import com.bumptech.glide.request.target.CustomTarget; +import com.bumptech.glide.request.target.Target; +import com.bumptech.glide.request.transition.Transition; + +import java.util.ArrayList; +import java.util.List; + +import app.fedilab.android.R; +import app.fedilab.android.activities.MainActivity; +import app.fedilab.android.client.API; +import app.fedilab.android.client.APIResponse; +import app.fedilab.android.client.Entities.Account; +import app.fedilab.android.client.Entities.Notification; +import app.fedilab.android.client.GNUAPI; +import app.fedilab.android.fragments.DisplayNotificationsFragment; + +import static app.fedilab.android.helper.BaseHelper.INTENT_ACTION; +import static app.fedilab.android.helper.BaseHelper.INTENT_TARGETED_ACCOUNT; +import static app.fedilab.android.helper.BaseHelper.NOTIFICATION_INTENT; +import static app.fedilab.android.helper.BaseHelper.PREF_INSTANCE; +import static app.fedilab.android.helper.BaseHelper.PREF_KEY_ID; +import static app.fedilab.android.helper.BaseHelper.getMainLogo; + +import static app.fedilab.android.helper.BaseHelper.notify_user; + + +public class NotificationsHelper { + + + public static void task(Context context, Account account) { + APIResponse apiResponse; + + + final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + //Check which notifications the user wants to see + boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true); + boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true); + boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true); + boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true); + boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true); + //User disagree with all notifications + if (!notif_follow && !notif_add && !notif_mention && !notif_share && !notif_poll) + return; //Nothing is done + //No account connected, the service is stopped + if (!Helper.isLoggedIn(context)) + return; + //If WIFI only and on WIFI OR user defined any connections to use the service. + if (!sharedpreferences.getBoolean(Helper.SET_WIFI_ONLY, false) || Helper.isOnWIFI(context)) { + if (account.getSocial().compareTo("FRIENDICA") != 0 && account.getSocial().compareTo("GNU") != 0) { + API api = new API(context, account.getInstance(), account.getToken()); + apiResponse = api.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, null, false); + } else { + GNUAPI gnuApi = new GNUAPI(context, account.getInstance(), account.getToken()); + apiResponse = gnuApi.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, null); + } + onRetrieveNotifications(context, apiResponse, account); + } + } + + + public static void onRetrieveNotifications(Context context, APIResponse apiResponse, final Account account) { + List notificationsReceived = apiResponse.getNotifications(); + + if (apiResponse.getError() != null || notificationsReceived == null || notificationsReceived.size() == 0 || account == null) + return; + final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true); + boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true); + boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true); + boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true); + boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true); + boolean notif_status = sharedpreferences.getBoolean(Helper.SET_NOTIF_STATUS, true); + final String max_id = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null); + final List notifications = new ArrayList<>(); + int pos = 0; + for (Notification notif : notificationsReceived) { + if (max_id == null || notif.getId().compareTo(max_id) > 0) { + notifications.add(pos, notif); + pos++; + } + } + if (notifications.size() == 0) + return; + //No previous notifications in cache, so no notification will be sent + int newFollows = 0; + int newAdds = 0; + int newMentions = 0; + int newShare = 0; + int newPolls = 0; + int newStatus = 0; + String notificationUrl = null; + String title = null; + final String message; + String targeted_account = null; + Helper.NotifType notifType = Helper.NotifType.MENTION; + + for (Notification notification : notifications) { + switch (notification.getType()) { + case "mention": + notifType = Helper.NotifType.MENTION; + if (notif_mention) { + newMentions++; + if (notificationUrl == null) { + notificationUrl = notification.getAccount().getAvatar(); + if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) + title = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_mention)); + else + title = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_mention)); + } + } + break; + case "status": + notifType = Helper.NotifType.STATUS; + if (notif_status) { + newStatus++; + if (notificationUrl == null) { + notificationUrl = notification.getAccount().getAvatar(); + if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) + title = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_status)); + else + title = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_status)); + } + } + break; + case "reblog": + notifType = Helper.NotifType.BOOST; + if (notif_share) { + newShare++; + if (notificationUrl == null) { + notificationUrl = notification.getAccount().getAvatar(); + if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) + title = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_reblog)); + else + title = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_reblog)); + + } + } + break; + case "favourite": + notifType = Helper.NotifType.FAV; + if (notif_add) { + newAdds++; + if (notificationUrl == null) { + notificationUrl = notification.getAccount().getAvatar(); + if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) + title = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_favourite)); + else + title = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_favourite)); + } + } + break; + case "follow_request": + notifType = Helper.NotifType.FOLLLOW; + if (notif_follow) { + newFollows++; + if (notificationUrl == null) { + notificationUrl = notification.getAccount().getAvatar(); + if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) + title = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_follow_request)); + else + title = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_follow_request)); + targeted_account = notification.getAccount().getId(); + } + } + break; + case "follow": + notifType = Helper.NotifType.FOLLLOW; + if (notif_follow) { + newFollows++; + if (notificationUrl == null) { + notificationUrl = notification.getAccount().getAvatar(); + if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) + title = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_follow)); + else + title = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_follow)); + targeted_account = notification.getAccount().getId(); + } + } + break; + case "poll": + notifType = Helper.NotifType.POLL; + if (notif_poll) { + newPolls++; + if (notificationUrl == null) { + notificationUrl = notification.getAccount().getAvatar(); + String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); + if (notification.getAccount().getId() != null && notification.getAccount().getId().equals(userId)) + title = context.getString(R.string.notif_poll_self); + else + title = context.getString(R.string.notif_poll); + } + } + break; + default: + } + } + + int allNotifCount = newFollows + newAdds + newMentions + newShare + newPolls + newStatus; + if (allNotifCount > 0) { + //Some others notification + int other = allNotifCount - 1; + if (other > 0) + message = context.getResources().getQuantityString(R.plurals.other_notifications, other, other); + else + message = ""; + final Intent intent = new Intent(context, MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); + intent.putExtra(INTENT_ACTION, NOTIFICATION_INTENT); + intent.putExtra(PREF_KEY_ID, account.getId()); + if (targeted_account != null && notifType == Helper.NotifType.FOLLLOW) + intent.putExtra(INTENT_TARGETED_ACCOUNT, targeted_account); + intent.putExtra(PREF_INSTANCE, account.getInstance()); + if (notificationUrl != null) { + final String finalTitle = title; + Handler mainHandler = new Handler(Looper.getMainLooper()); + + final String finalNotificationUrl = notificationUrl; + Helper.NotifType finalNotifType = notifType; + Runnable myRunnable = () -> Glide.with(context) + .asBitmap() + .load(finalNotificationUrl) + .listener(new RequestListener() { + + @Override + public boolean onResourceReady(Bitmap resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { + return false; + } + + @Override + public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { + + notify_user(context, account, intent, BitmapFactory.decodeResource(context.getResources(), + getMainLogo(context)), finalNotifType, finalTitle, message); + String lastNotif = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null); + if (lastNotif == null || notifications.get(0).getId().compareTo(lastNotif) > 0) { + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), notifications.get(0).getId()); + editor.apply(); + } + return false; + } + }) + .into(new CustomTarget() { + @Override + public void onResourceReady(@NonNull Bitmap resource, Transition transition) { + notify_user(context, account, intent, resource, finalNotifType, finalTitle, message); + String lastNotif = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null); + if (lastNotif == null || notifications.get(0).getId().compareTo(lastNotif) > 0) { + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), notifications.get(0).getId()); + editor.apply(); + } + } + + @Override + public void onLoadCleared(@Nullable Drawable placeholder) { + + } + }); + mainHandler.post(myRunnable); + + } + + } + } } diff --git a/app/src/main/java/app/fedilab/android/jobs/BaseNotificationsSyncJob.java b/app/src/main/java/app/fedilab/android/jobs/BaseNotificationsSyncJob.java index 78217c54a..59a20d5dc 100644 --- a/app/src/main/java/app/fedilab/android/jobs/BaseNotificationsSyncJob.java +++ b/app/src/main/java/app/fedilab/android/jobs/BaseNotificationsSyncJob.java @@ -52,6 +52,7 @@ import app.fedilab.android.client.Entities.Notification; import app.fedilab.android.client.GNUAPI; import app.fedilab.android.fragments.DisplayNotificationsFragment; import app.fedilab.android.helper.Helper; +import app.fedilab.android.helper.NotificationsHelper; import app.fedilab.android.sqlite.AccountDAO; import app.fedilab.android.sqlite.Sqlite; @@ -145,215 +146,8 @@ public class BaseNotificationsSyncJob extends Job { GNUAPI gnuApi = new GNUAPI(getContext(), account.getInstance(), account.getToken()); apiResponse = gnuApi.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, null); } - onRetrieveNotifications(apiResponse, account); + NotificationsHelper.onRetrieveNotifications(getContext(), apiResponse, account); } } } - - - private void onRetrieveNotifications(APIResponse apiResponse, final Account account) { - List notificationsReceived = apiResponse.getNotifications(); - if (apiResponse.getError() != null || notificationsReceived == null || notificationsReceived.size() == 0 || account == null) - return; - final SharedPreferences sharedpreferences = getContext().getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true); - boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true); - boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true); - boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true); - boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true); - boolean notif_status = sharedpreferences.getBoolean(Helper.SET_NOTIF_STATUS, true); - final String max_id = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null); - final List notifications = new ArrayList<>(); - int pos = 0; - for (Notification notif : notificationsReceived) { - if (max_id == null || notif.getId().compareTo(max_id) > 0) { - notifications.add(pos, notif); - pos++; - } - } - if (notifications.size() == 0) - return; - //No previous notifications in cache, so no notification will be sent - int newFollows = 0; - int newAdds = 0; - int newMentions = 0; - int newShare = 0; - int newPolls = 0; - int newStatus = 0; - String notificationUrl = null; - String title = null; - final String message; - String targeted_account = null; - Helper.NotifType notifType = Helper.NotifType.MENTION; - - for (Notification notification : notifications) { - switch (notification.getType()) { - case "mention": - notifType = Helper.NotifType.MENTION; - if (notif_mention) { - newMentions++; - if (notificationUrl == null) { - notificationUrl = notification.getAccount().getAvatar(); - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - title = String.format("%s %s", notification.getAccount().getDisplay_name(), getContext().getString(R.string.notif_mention)); - else - title = String.format("@%s %s", notification.getAccount().getAcct(), getContext().getString(R.string.notif_mention)); - } - } - break; - case "status": - notifType = Helper.NotifType.STATUS; - if (notif_status) { - newStatus++; - if (notificationUrl == null) { - notificationUrl = notification.getAccount().getAvatar(); - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - title = String.format("%s %s", notification.getAccount().getDisplay_name(), getContext().getString(R.string.notif_status)); - else - title = String.format("@%s %s", notification.getAccount().getAcct(), getContext().getString(R.string.notif_status)); - } - } - break; - case "reblog": - notifType = Helper.NotifType.BOOST; - if (notif_share) { - newShare++; - if (notificationUrl == null) { - notificationUrl = notification.getAccount().getAvatar(); - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - title = String.format("%s %s", notification.getAccount().getDisplay_name(), getContext().getString(R.string.notif_reblog)); - else - title = String.format("@%s %s", notification.getAccount().getAcct(), getContext().getString(R.string.notif_reblog)); - - } - } - break; - case "favourite": - notifType = Helper.NotifType.FAV; - if (notif_add) { - newAdds++; - if (notificationUrl == null) { - notificationUrl = notification.getAccount().getAvatar(); - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - title = String.format("%s %s", notification.getAccount().getDisplay_name(), getContext().getString(R.string.notif_favourite)); - else - title = String.format("@%s %s", notification.getAccount().getAcct(), getContext().getString(R.string.notif_favourite)); - } - } - break; - case "follow_request": - notifType = Helper.NotifType.FOLLLOW; - if (notif_follow) { - newFollows++; - if (notificationUrl == null) { - notificationUrl = notification.getAccount().getAvatar(); - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - title = String.format("%s %s", notification.getAccount().getDisplay_name(), getContext().getString(R.string.notif_follow_request)); - else - title = String.format("@%s %s", notification.getAccount().getAcct(), getContext().getString(R.string.notif_follow_request)); - targeted_account = notification.getAccount().getId(); - } - } - break; - case "follow": - notifType = Helper.NotifType.FOLLLOW; - if (notif_follow) { - newFollows++; - if (notificationUrl == null) { - notificationUrl = notification.getAccount().getAvatar(); - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - title = String.format("%s %s", notification.getAccount().getDisplay_name(), getContext().getString(R.string.notif_follow)); - else - title = String.format("@%s %s", notification.getAccount().getAcct(), getContext().getString(R.string.notif_follow)); - targeted_account = notification.getAccount().getId(); - } - } - break; - case "poll": - notifType = Helper.NotifType.POLL; - if (notif_poll) { - newPolls++; - if (notificationUrl == null) { - notificationUrl = notification.getAccount().getAvatar(); - String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); - if (notification.getAccount().getId() != null && notification.getAccount().getId().equals(userId)) - title = getContext().getString(R.string.notif_poll_self); - else - title = getContext().getString(R.string.notif_poll); - } - } - break; - default: - } - } - - int allNotifCount = newFollows + newAdds + newMentions + newShare + newPolls + newStatus; - if (allNotifCount > 0) { - //Some others notification - int other = allNotifCount - 1; - if (other > 0) - message = getContext().getResources().getQuantityString(R.plurals.other_notifications, other, other); - else - message = ""; - final Intent intent = new Intent(getContext(), MainActivity.class); - intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); - intent.putExtra(INTENT_ACTION, NOTIFICATION_INTENT); - intent.putExtra(PREF_KEY_ID, account.getId()); - if (targeted_account != null && notifType == Helper.NotifType.FOLLLOW) - intent.putExtra(INTENT_TARGETED_ACCOUNT, targeted_account); - intent.putExtra(PREF_INSTANCE, account.getInstance()); - if (notificationUrl != null) { - final String finalTitle = title; - Handler mainHandler = new Handler(Looper.getMainLooper()); - - final String finalNotificationUrl = notificationUrl; - Helper.NotifType finalNotifType = notifType; - Runnable myRunnable = () -> Glide.with(getContext()) - .asBitmap() - .load(finalNotificationUrl) - .listener(new RequestListener() { - - @Override - public boolean onResourceReady(Bitmap resource, Object model, Target target, DataSource dataSource, boolean isFirstResource) { - return false; - } - - @Override - public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { - - notify_user(getContext(), account, intent, BitmapFactory.decodeResource(getContext().getResources(), - getMainLogo(getContext())), finalNotifType, finalTitle, message); - String lastNotif = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null); - if (lastNotif == null || notifications.get(0).getId().compareTo(lastNotif) > 0) { - SharedPreferences.Editor editor = sharedpreferences.edit(); - editor.putString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), notifications.get(0).getId()); - editor.apply(); - } - return false; - } - }) - .into(new CustomTarget() { - @Override - public void onResourceReady(@NonNull Bitmap resource, Transition transition) { - notify_user(getContext(), account, intent, resource, finalNotifType, finalTitle, message); - String lastNotif = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null); - if (lastNotif == null || notifications.get(0).getId().compareTo(lastNotif) > 0) { - SharedPreferences.Editor editor = sharedpreferences.edit(); - editor.putString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), notifications.get(0).getId()); - editor.apply(); - } - } - - @Override - public void onLoadCleared(@Nullable Drawable placeholder) { - - } - }); - mainHandler.post(myRunnable); - - } - - } - } - } \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java index 8595731da..14be510b2 100644 --- a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java +++ b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java @@ -15,12 +15,25 @@ package app.fedilab.android.services; * see . */ import android.content.Context; import android.content.SharedPreferences; +import android.database.sqlite.SQLiteDatabase; + import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.unifiedpush.android.connector.MessagingReceiver; import org.unifiedpush.android.connector.MessagingReceiverHandler; + +import java.util.List; + +import app.fedilab.android.client.API; +import app.fedilab.android.client.APIResponse; +import app.fedilab.android.client.Entities.Account; +import app.fedilab.android.client.GNUAPI; +import app.fedilab.android.fragments.DisplayNotificationsFragment; import app.fedilab.android.helper.Helper; +import app.fedilab.android.helper.NotificationsHelper; import app.fedilab.android.helper.PushNotifications; +import app.fedilab.android.sqlite.AccountDAO; +import app.fedilab.android.sqlite.Sqlite; import static android.content.Context.MODE_PRIVATE; @@ -28,13 +41,27 @@ class handler implements MessagingReceiverHandler { @Override - public void onMessage(@Nullable Context context, @NotNull String s, @NotNull String s1) { - new PushNotifications() - .displayNotification(context, s); + public void onMessage(@Nullable Context context, @NotNull String s) { + SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + final List accounts = new AccountDAO(context, db).getPushNotificationAccounts(); + new Thread(() -> { + for (Account account : accounts) { + APIResponse apiResponse; + if (account.getSocial().compareTo("FRIENDICA") != 0 && account.getSocial().compareTo("GNU") != 0) { + API api = new API(context, account.getInstance(), account.getToken()); + apiResponse = api.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, null, false); + } else { + GNUAPI gnuApi = new GNUAPI(context, account.getInstance(), account.getToken()); + apiResponse = gnuApi.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, null); + } + NotificationsHelper.onRetrieveNotifications(context, apiResponse, account); + } + }).start(); + } @Override - public void onNewEndpoint(@Nullable Context context, @NotNull String endpoint, @NotNull String s1) { + public void onNewEndpoint(@Nullable Context context, @NotNull String endpoint) { final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); SharedPreferences.Editor editor = sharedpreferences.edit(); editor.putString(Helper.SERVER_ENDPOINT, endpoint); @@ -45,17 +72,17 @@ class handler implements MessagingReceiverHandler { } @Override - public void onRegistrationFailed(@Nullable Context context, @NotNull String s) { + public void onRegistrationFailed(@Nullable Context context) { } @Override - public void onRegistrationRefused(@Nullable Context context, @NotNull String s) { + public void onRegistrationRefused(@Nullable Context context) { } @Override - public void onUnregistered(@Nullable Context context, @NotNull String s) { + public void onUnregistered(@Nullable Context context) { } } diff --git a/app/src/main/res/layout/fragment_settings_notifications.xml b/app/src/main/res/layout/fragment_settings_notifications.xml index 57514173e..550392e07 100644 --- a/app/src/main/res/layout/fragment_settings_notifications.xml +++ b/app/src/main/res/layout/fragment_settings_notifications.xml @@ -89,102 +89,6 @@ android:layout_marginBottom="10dp" android:textColor="@color/mastodonC2" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Information below may reflect the user\'s profile incompletely. Insert emoji The app did not collect custom emojis for the moment. - Live notifications + Push notifications Are you sure you want to logout? Are you sure you want to logout @%1$s@%2$s? @@ -984,7 +984,6 @@ If disabled, sensitive media will be hidden with a button Store media in full size with a long press on previews Add an ellipse button at the top right for listing all tags/instances/lists - Keep an open connection to the streaming API for live notifications. During the time slot, the app will send notifications. You can reverse (ie: silent) this time slot with the right spinner. Display a Fedilab button below profile picture. It is a shortcut for accessing in-app features. Allow to reply directly in timelines below statuses @@ -1121,8 +1120,7 @@ Hide Fedilab notification bar For hiding the remaining notification in the status bar, tap on the eye icon button then uncheck: \"Display in status bar\" - Notifications will be delayed every 30 seconds. That will allow to drain less battery. - Live notifications delayed + Use a push notifications system for getting notifications in real time. No live notifications Notifications will be fetched every 15 minutes. Add notes From 7402ba0779f12ace4e670f380404d47fc9fcbd1f Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 28 Feb 2021 11:47:52 +0100 Subject: [PATCH 21/35] working push --- app/src/main/java/app/fedilab/android/helper/ECDH.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/app/fedilab/android/helper/ECDH.java b/app/src/main/java/app/fedilab/android/helper/ECDH.java index 582e76e5e..c7873e0ac 100644 --- a/app/src/main/java/app/fedilab/android/helper/ECDH.java +++ b/app/src/main/java/app/fedilab/android/helper/ECDH.java @@ -61,7 +61,6 @@ public class ECDH { private static final String kp_private = "kp_private"; public static final String KEGEN_ALG = "ECDH"; - private static final String TAG = ECDH.class.getSimpleName(); private static final String kp_public_affine_x = "kp_public_affine_x"; private static final String kp_public_affine_y = "kp_public_affine_y"; From df51e7351cebd704583810ecac4072a2184b2c5a Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 1 Mar 2021 11:12:01 +0100 Subject: [PATCH 22/35] working push --- app/build.gradle | 2 +- .../asynctasks/PostSubscriptionAsyncTask.java | 5 +- .../java/app/fedilab/android/client/API.java | 16 ++- .../fedilab/android/helper/BaseHelper.java | 20 +-- .../java/app/fedilab/android/helper/ECDH.java | 9 +- .../android/helper/NotificationsHelper.java | 135 +++++++++--------- .../android/helper/PushNotifications.java | 15 +- .../android/services/UnifiedPushService.java | 39 +++-- .../fedilab/android/sqlite/AccountDAO.java | 16 +++ 9 files changed, 140 insertions(+), 117 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 47c462ded..ae793a0c4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -177,7 +177,7 @@ dependencies { implementation "com.madgag.spongycastle:bctls-jdk15on:1.58.0.0" //implementation 'org.bouncycastle:bcprov-jdk15on:1.64' - implementation 'com.github.UnifiedPush:android-connector:1.0.0' + implementation 'com.github.p1gp1g:android-connector-1:1.1.0-beta1' //Flavors //Playstore diff --git a/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java b/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java index b9acfea3e..511cc32cf 100644 --- a/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java +++ b/app/src/main/java/app/fedilab/android/asynctasks/PostSubscriptionAsyncTask.java @@ -40,10 +40,7 @@ public class PostSubscriptionAsyncTask { protected void doInBackground() { new Thread(() -> { - apiResponse = new API(contextReference.get(), account.getInstance(), account.getToken()).getPushSubscription(); - if (apiResponse == null || apiResponse.getPushSubscription() == null || endpoint.compareTo(apiResponse.getPushSubscription().getEndpoint()) != 0) { - apiResponse = new API(contextReference.get(), account.getInstance(), account.getToken()).pushSubscription(endpoint, account); - } + apiResponse = new API(contextReference.get(), account.getInstance(), account.getToken()).pushSubscription(endpoint, account); }).start(); } diff --git a/app/src/main/java/app/fedilab/android/client/API.java b/app/src/main/java/app/fedilab/android/client/API.java index 632271be6..460176393 100644 --- a/app/src/main/java/app/fedilab/android/client/API.java +++ b/app/src/main/java/app/fedilab/android/client/API.java @@ -22,9 +22,10 @@ import android.os.Build; import android.os.Bundle; import android.text.Html; import android.text.SpannableString; -import android.util.Base64; +import android.util.Log; import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import androidx.preference.PreferenceManager; import com.google.gson.GsonBuilder; @@ -111,6 +112,10 @@ import app.fedilab.android.sqlite.AccountDAO; import app.fedilab.android.sqlite.Sqlite; import app.fedilab.android.sqlite.TimelineCacheDAO; +import static app.fedilab.android.helper.ECDH.kp_private; +import static app.fedilab.android.helper.ECDH.kp_public; +import static app.fedilab.android.helper.ECDH.newPair; + /** * Created by Thomas on 23/04/2017. @@ -5114,7 +5119,6 @@ public class API { private APIResponse getNotifications(DisplayNotificationsFragment.Type type, String max_id, String since_id, int limit, boolean display) { HashMap params = new HashMap<>(); - if (MainActivity.social == UpdateAccountInfoAsyncTask.SOCIAL.PIXELFED) { params.put("pg", "true"); params.put("page", max_id); @@ -5858,6 +5862,13 @@ public class API { boolean notif_status = sharedpreferences.getBoolean(Helper.SET_NOTIF_STATUS, true); boolean notif_poll = sharedpreferences.getBoolean(Helper.SET_NOTIF_POLL, true); + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); + String strPub = prefs.getString(kp_public, ""); + String strPriv = prefs.getString(kp_private, ""); + if (strPub.trim().isEmpty() || strPriv.trim().isEmpty()) { + newPair(context); + } ECDH ecdh = ECDH.getInstance(); String pubKey = ecdh.getPublicKey(context); @@ -5868,7 +5879,6 @@ public class API { try { JSONObject jsonObjectSub = new JSONObject(); - jsonObjectSub.put("endpoint", endpoint); JSONObject jsonObjectkey = new JSONObject(); jsonObjectkey.put("p256dh", pubKey); diff --git a/app/src/main/java/app/fedilab/android/helper/BaseHelper.java b/app/src/main/java/app/fedilab/android/helper/BaseHelper.java index 7f0104f0d..94583aca5 100644 --- a/app/src/main/java/app/fedilab/android/helper/BaseHelper.java +++ b/app/src/main/java/app/fedilab/android/helper/BaseHelper.java @@ -240,6 +240,7 @@ import static app.fedilab.android.helper.ThemeHelper.getAttColor; import static app.fedilab.android.sqlite.StatusCacheDAO.ARCHIVE_CACHE; import static app.fedilab.android.sqlite.StatusCacheDAO.BOOKMARK_CACHE; import static app.fedilab.android.webview.ProxyHelper.setProxy; +import static cafe.adriel.androidaudiorecorder.Util.formatSeconds; import static com.koushikdutta.async.util.StreamUtility.copyStream; @@ -598,19 +599,22 @@ public class BaseHelper { public static void startStreaming(Context context) { int liveNotifications = liveNotifType(context); + ApplicationJob.cancelAllJob(NotificationsSyncJob.NOTIFICATION_REFRESH); + NotificationsSyncJob.schedule(false); switch (liveNotifications) { case NOTIF_PUSH: - if (social == UpdateAccountInfoAsyncTask.SOCIAL.MASTODON || social == UpdateAccountInfoAsyncTask.SOCIAL.PLEROMA) { - new Registration().registerAppWithDialog(context); - ApplicationJob.cancelAllJob(NotificationsSyncJob.NOTIFICATION_REFRESH); - } else { - new Registration().unregisterApp(context); - NotificationsSyncJob.schedule(false); - } + new Thread(() -> { + SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + List accounts = new AccountDAO(context, db).getPushNotificationAccounts(); + ((Activity) context).runOnUiThread(() -> { + for (Account account : accounts) { + new Registration().registerAppWithDialog(context, account.getUsername() + "@" + account.getInstance()); + } + }); + }).start(); break; case NOTIF_NONE: new Registration().unregisterApp(context); - NotificationsSyncJob.schedule(false); break; } } diff --git a/app/src/main/java/app/fedilab/android/helper/ECDH.java b/app/src/main/java/app/fedilab/android/helper/ECDH.java index c7873e0ac..674b48188 100644 --- a/app/src/main/java/app/fedilab/android/helper/ECDH.java +++ b/app/src/main/java/app/fedilab/android/helper/ECDH.java @@ -18,6 +18,7 @@ import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.util.Base64; +import android.util.Log; import androidx.preference.PreferenceManager; @@ -54,11 +55,11 @@ import app.fedilab.android.client.Entities.Account; public class ECDH { - private static final String kp_public = "kp_public"; + public static final String kp_public = "kp_public"; public static final String peer_public = "peer_public"; public static final String PROVIDER = org.spongycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME; - private static final String kp_private = "kp_private"; + public static final String kp_private = "kp_private"; public static final String KEGEN_ALG = "ECDH"; private static final String kp_public_affine_x = "kp_public_affine_x"; @@ -127,7 +128,7 @@ public class ECDH { } @SuppressLint("ApplySharedPref") - static KeyPair newPair(Context context) { + public static KeyPair newPair(Context context) { SharedPreferences.Editor prefsEditor = PreferenceManager .getDefaultSharedPreferences(context).edit(); KeyPair kp; @@ -214,7 +215,7 @@ public class ECDH { .getDefaultSharedPreferences(context); String strPub = prefs.getString(kp_public, ""); String strPriv = prefs.getString(kp_private, ""); - if (strPub.isEmpty() || strPriv.isEmpty()) { + if (strPub.trim().isEmpty() || strPriv.trim().isEmpty()) { return newPair(context); } try { diff --git a/app/src/main/java/app/fedilab/android/helper/NotificationsHelper.java b/app/src/main/java/app/fedilab/android/helper/NotificationsHelper.java index 61f77db2c..40c52ccc0 100644 --- a/app/src/main/java/app/fedilab/android/helper/NotificationsHelper.java +++ b/app/src/main/java/app/fedilab/android/helper/NotificationsHelper.java @@ -20,8 +20,12 @@ import android.content.SharedPreferences; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.Handler; import android.os.Looper; +import android.text.Html; +import android.text.SpannableString; +import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -46,6 +50,7 @@ import app.fedilab.android.client.Entities.Notification; import app.fedilab.android.client.GNUAPI; import app.fedilab.android.fragments.DisplayNotificationsFragment; +import static android.text.Html.FROM_HTML_MODE_LEGACY; import static app.fedilab.android.helper.BaseHelper.INTENT_ACTION; import static app.fedilab.android.helper.BaseHelper.INTENT_TARGETED_ACCOUNT; import static app.fedilab.android.helper.BaseHelper.NOTIFICATION_INTENT; @@ -92,7 +97,6 @@ public class NotificationsHelper { public static void onRetrieveNotifications(Context context, APIResponse apiResponse, final Account account) { List notificationsReceived = apiResponse.getNotifications(); - if (apiResponse.getError() != null || notificationsReceived == null || notificationsReceived.size() == 0 || account == null) return; final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); @@ -122,119 +126,118 @@ public class NotificationsHelper { int newStatus = 0; String notificationUrl = null; String title = null; - final String message; + String message = null; String targeted_account = null; Helper.NotifType notifType = Helper.NotifType.MENTION; - + String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); for (Notification notification : notifications) { switch (notification.getType()) { case "mention": notifType = Helper.NotifType.MENTION; if (notif_mention) { - newMentions++; - if (notificationUrl == null) { - notificationUrl = notification.getAccount().getAvatar(); - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - title = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_mention)); - else - title = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_mention)); + if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) + message = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_mention)); + else + message = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_mention)); + if (notification.getStatus() != null) { + if (notification.getStatus().getSpoiler_text() != null && notification.getStatus().getSpoiler_text().length() > 0) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text(), FROM_HTML_MODE_LEGACY)); + else + message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text())); + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent(), FROM_HTML_MODE_LEGACY)); + else + message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent())); + } } + newFollows++; } break; case "status": notifType = Helper.NotifType.STATUS; if (notif_status) { - newStatus++; - if (notificationUrl == null) { - notificationUrl = notification.getAccount().getAvatar(); - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - title = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_status)); - else - title = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_status)); + if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) + message = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_status)); + else + message = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_status)); + if (notification.getStatus() != null) { + if (notification.getStatus().getSpoiler_text() != null && notification.getStatus().getSpoiler_text().length() > 0) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text(), FROM_HTML_MODE_LEGACY)); + else + message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getSpoiler_text())); + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent(), FROM_HTML_MODE_LEGACY)); + else + message = "\n" + new SpannableString(Html.fromHtml(notification.getStatus().getContent())); + } } + newStatus++; } break; case "reblog": notifType = Helper.NotifType.BOOST; if (notif_share) { + if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) + message = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_reblog)); + else + message = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_reblog)); newShare++; - if (notificationUrl == null) { - notificationUrl = notification.getAccount().getAvatar(); - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - title = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_reblog)); - else - title = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_reblog)); - - } } break; case "favourite": notifType = Helper.NotifType.FAV; if (notif_add) { + if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) + message = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_favourite)); + else + message = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_favourite)); newAdds++; - if (notificationUrl == null) { - notificationUrl = notification.getAccount().getAvatar(); - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - title = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_favourite)); - else - title = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_favourite)); - } } break; case "follow_request": notifType = Helper.NotifType.FOLLLOW; if (notif_follow) { + if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) + message = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_follow_request)); + else + message = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_follow_request)); + targeted_account = notification.getAccount().getId(); newFollows++; - if (notificationUrl == null) { - notificationUrl = notification.getAccount().getAvatar(); - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - title = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_follow_request)); - else - title = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_follow_request)); - targeted_account = notification.getAccount().getId(); - } } break; case "follow": notifType = Helper.NotifType.FOLLLOW; if (notif_follow) { + if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) + message = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_follow)); + else + message = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_follow)); + targeted_account = notification.getAccount().getId(); newFollows++; - if (notificationUrl == null) { - notificationUrl = notification.getAccount().getAvatar(); - if (notification.getAccount().getDisplay_name() != null && notification.getAccount().getDisplay_name().length() > 0) - title = String.format("%s %s", notification.getAccount().getDisplay_name(), context.getString(R.string.notif_follow)); - else - title = String.format("@%s %s", notification.getAccount().getAcct(), context.getString(R.string.notif_follow)); - targeted_account = notification.getAccount().getId(); - } } break; case "poll": notifType = Helper.NotifType.POLL; if (notif_poll) { + if (notification.getAccount().getId() != null && notification.getAccount().getId().equals(userId)) + message = context.getString(R.string.notif_poll_self); + else + message = context.getString(R.string.notif_poll); newPolls++; - if (notificationUrl == null) { - notificationUrl = notification.getAccount().getAvatar(); - String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); - if (notification.getAccount().getId() != null && notification.getAccount().getId().equals(userId)) - title = context.getString(R.string.notif_poll_self); - else - title = context.getString(R.string.notif_poll); - } } break; default: } + } int allNotifCount = newFollows + newAdds + newMentions + newShare + newPolls + newStatus; if (allNotifCount > 0) { //Some others notification - int other = allNotifCount - 1; - if (other > 0) - message = context.getResources().getQuantityString(R.plurals.other_notifications, other, other); - else - message = ""; final Intent intent = new Intent(context, MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(INTENT_ACTION, NOTIFICATION_INTENT); @@ -242,12 +245,15 @@ public class NotificationsHelper { if (targeted_account != null && notifType == Helper.NotifType.FOLLLOW) intent.putExtra(INTENT_TARGETED_ACCOUNT, targeted_account); intent.putExtra(PREF_INSTANCE, account.getInstance()); + notificationUrl = notifications.get(0).getAccount().getAvatar(); if (notificationUrl != null) { - final String finalTitle = title; + Handler mainHandler = new Handler(Looper.getMainLooper()); final String finalNotificationUrl = notificationUrl; Helper.NotifType finalNotifType = notifType; + String finalMessage = message; + String finalMessage1 = message; Runnable myRunnable = () -> Glide.with(context) .asBitmap() .load(finalNotificationUrl) @@ -260,9 +266,8 @@ public class NotificationsHelper { @Override public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) { - notify_user(context, account, intent, BitmapFactory.decodeResource(context.getResources(), - getMainLogo(context)), finalNotifType, finalTitle, message); + getMainLogo(context)), finalNotifType, context.getString(R.string.top_notification), finalMessage1); String lastNotif = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null); if (lastNotif == null || notifications.get(0).getId().compareTo(lastNotif) > 0) { SharedPreferences.Editor editor = sharedpreferences.edit(); @@ -275,7 +280,7 @@ public class NotificationsHelper { .into(new CustomTarget() { @Override public void onResourceReady(@NonNull Bitmap resource, Transition transition) { - notify_user(context, account, intent, resource, finalNotifType, finalTitle, message); + notify_user(context, account, intent, resource, finalNotifType, context.getString(R.string.top_notification), finalMessage); String lastNotif = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null); if (lastNotif == null || notifications.get(0).getId().compareTo(lastNotif) > 0) { SharedPreferences.Editor editor = sharedpreferences.edit(); diff --git a/app/src/main/java/app/fedilab/android/helper/PushNotifications.java b/app/src/main/java/app/fedilab/android/helper/PushNotifications.java index 146b68aab..fd1902003 100644 --- a/app/src/main/java/app/fedilab/android/helper/PushNotifications.java +++ b/app/src/main/java/app/fedilab/android/helper/PushNotifications.java @@ -15,9 +15,6 @@ package app.fedilab.android.helper; * see . */ import android.content.Context; import android.database.sqlite.SQLiteDatabase; - - -import java.util.List; import app.fedilab.android.asynctasks.PostSubscriptionAsyncTask; import app.fedilab.android.client.Entities.Account; @@ -28,17 +25,13 @@ import app.fedilab.android.sqlite.Sqlite; public class PushNotifications { - public void registerPushNotifications(Context context, String endpoint) { + public void registerPushNotifications(Context context, String endpoint, String slug) { SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); - List accountPush = new AccountDAO(context, db).getPushNotificationAccounts(); - for (Account account : accountPush) { - new PostSubscriptionAsyncTask(context, account, endpoint); - } + String[] slugArray = slug.split("@"); + Account account = new AccountDAO(context, db).getUniqAccountUsernameInstance(slugArray[0], slugArray[1]); + new PostSubscriptionAsyncTask(context, account, endpoint); } - public void displayNotification(Context context, String ciphered) { - - } } diff --git a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java index 14be510b2..cf63316d5 100644 --- a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java +++ b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java @@ -16,13 +16,13 @@ package app.fedilab.android.services; import android.content.Context; import android.content.SharedPreferences; import android.database.sqlite.SQLiteDatabase; +import android.util.Log; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.unifiedpush.android.connector.MessagingReceiver; import org.unifiedpush.android.connector.MessagingReceiverHandler; -import java.util.List; import app.fedilab.android.client.API; import app.fedilab.android.client.APIResponse; @@ -41,48 +41,45 @@ class handler implements MessagingReceiverHandler { @Override - public void onMessage(@Nullable Context context, @NotNull String s) { - SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); - final List accounts = new AccountDAO(context, db).getPushNotificationAccounts(); + public void onMessage(@Nullable Context context, @NotNull String s, @NotNull String slug) { new Thread(() -> { - for (Account account : accounts) { - APIResponse apiResponse; - if (account.getSocial().compareTo("FRIENDICA") != 0 && account.getSocial().compareTo("GNU") != 0) { - API api = new API(context, account.getInstance(), account.getToken()); - apiResponse = api.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, null, false); - } else { - GNUAPI gnuApi = new GNUAPI(context, account.getInstance(), account.getToken()); - apiResponse = gnuApi.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, null); - } - NotificationsHelper.onRetrieveNotifications(context, apiResponse, account); + SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + String[] slugArray = slug.split("@"); + Account account = new AccountDAO(context, db).getUniqAccountUsernameInstance(slugArray[0], slugArray[1]); + APIResponse apiResponse; + if (account.getSocial().compareTo("FRIENDICA") != 0 && account.getSocial().compareTo("GNU") != 0) { + API api = new API(context, account.getInstance(), account.getToken()); + apiResponse = api.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, null, false); + } else { + GNUAPI gnuApi = new GNUAPI(context, account.getInstance(), account.getToken()); + apiResponse = gnuApi.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, null); } + NotificationsHelper.onRetrieveNotifications(context, apiResponse, account); }).start(); - } @Override - public void onNewEndpoint(@Nullable Context context, @NotNull String endpoint) { + public void onNewEndpoint(@Nullable Context context, @NotNull String endpoint, @NotNull String slug) { final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); SharedPreferences.Editor editor = sharedpreferences.edit(); editor.putString(Helper.SERVER_ENDPOINT, endpoint); editor.apply(); - new PushNotifications() - .registerPushNotifications(context, endpoint); + .registerPushNotifications(context, endpoint, slug); } @Override - public void onRegistrationFailed(@Nullable Context context) { + public void onRegistrationFailed(@Nullable Context context, @NotNull String s) { } @Override - public void onRegistrationRefused(@Nullable Context context) { + public void onRegistrationRefused(@Nullable Context context, @NotNull String s) { } @Override - public void onUnregistered(@Nullable Context context) { + public void onUnregistered(@Nullable Context context, @NotNull String s) { } } diff --git a/app/src/main/java/app/fedilab/android/sqlite/AccountDAO.java b/app/src/main/java/app/fedilab/android/sqlite/AccountDAO.java index 642057991..d2ade4fce 100644 --- a/app/src/main/java/app/fedilab/android/sqlite/AccountDAO.java +++ b/app/src/main/java/app/fedilab/android/sqlite/AccountDAO.java @@ -327,6 +327,22 @@ public class AccountDAO { } } + /** + * Returns an Account by token + * + * @param userName String + * @param instance String + * @return Account + */ + public Account getUniqAccountUsernameInstance(String userName, String instance) { + + try { + Cursor c = db.query(Sqlite.TABLE_USER_ACCOUNT, null, Sqlite.COL_USERNAME + " = \"" + userName + "\" AND " + Sqlite.COL_INSTANCE + " = \"" + instance + "\"", null, null, null, null, "1"); + return cursorToUser(c); + } catch (Exception e) { + return null; + } + } /** * Test if the current user is already stored in data base From 19bcdaa25ec80c4256f8858f97429ba3e8ee480c Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 1 Mar 2021 12:08:21 +0100 Subject: [PATCH 23/35] copy release notes --- .../java/app/fedilab/android/helper/ECDH.java | 0 .../java/app/fedilab/android/helper/ECDH.java | 261 ++++++++++++++++++ 2 files changed, 261 insertions(+) rename app/src/{main => fdroidcommon}/java/app/fedilab/android/helper/ECDH.java (100%) create mode 100644 app/src/playstore/java/app/fedilab/android/helper/ECDH.java diff --git a/app/src/main/java/app/fedilab/android/helper/ECDH.java b/app/src/fdroidcommon/java/app/fedilab/android/helper/ECDH.java similarity index 100% rename from app/src/main/java/app/fedilab/android/helper/ECDH.java rename to app/src/fdroidcommon/java/app/fedilab/android/helper/ECDH.java diff --git a/app/src/playstore/java/app/fedilab/android/helper/ECDH.java b/app/src/playstore/java/app/fedilab/android/helper/ECDH.java new file mode 100644 index 000000000..674b48188 --- /dev/null +++ b/app/src/playstore/java/app/fedilab/android/helper/ECDH.java @@ -0,0 +1,261 @@ +package app.fedilab.android.helper; +/* Copyright 2021 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Base64; +import android.util.Log; + +import androidx.preference.PreferenceManager; + + +import org.spongycastle.asn1.ASN1ObjectIdentifier; +import org.spongycastle.asn1.x9.ECNamedCurveTable; +import org.spongycastle.asn1.x9.X9ECParameters; +import org.spongycastle.crypto.params.ECNamedDomainParameters; +import org.spongycastle.crypto.params.ECPrivateKeyParameters; +import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.spongycastle.jce.spec.ECNamedCurveSpec; +import org.spongycastle.math.ec.ECCurve; +import org.spongycastle.math.ec.ECPoint; + +import java.math.BigInteger; +import java.security.KeyFactory; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.Security; +import java.security.interfaces.ECPublicKey; +import java.security.spec.ECGenParameterSpec; +import java.security.spec.X509EncodedKeySpec; + +import javax.crypto.KeyAgreement; + +import app.fedilab.android.client.Entities.Account; + +// https://github.com/nelenkov/ecdh-kx/blob/master/src/org/nick/ecdhkx/Crypto.java + +public class ECDH { + + + public static final String kp_public = "kp_public"; + public static final String peer_public = "peer_public"; + public static final String PROVIDER = org.spongycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME; + + public static final String kp_private = "kp_private"; + public static final String KEGEN_ALG = "ECDH"; + + private static final String kp_public_affine_x = "kp_public_affine_x"; + private static final String kp_public_affine_y = "kp_public_affine_y"; + + private static ECDH instance; + + + static { + Security.addProvider(new org.spongycastle.jce.provider.BouncyCastleProvider()); + } + + public static KeyFactory kf = null; + private static KeyPairGenerator kpg = null; + + + public ECDH() { + try { + kf = KeyFactory.getInstance(KEGEN_ALG, PROVIDER); + kpg = KeyPairGenerator.getInstance(KEGEN_ALG, PROVIDER); + } catch (NoSuchAlgorithmException | NoSuchProviderException e) { + throw new RuntimeException(e); + } + } + + public static synchronized ECDH getInstance() { + if (instance == null) { + instance = new ECDH(); + } + + return instance; + } + + public static String base64Encode(byte[] b) { + + byte[] encoded = Base64.encode( + b, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP); + return new String(encoded); + } + + static byte[] base64Decode(String str) { + return Base64.decode(str, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP); + } + + static synchronized KeyPair generateKeyPair() + throws Exception { + ECGenParameterSpec ecParamSpec = new ECGenParameterSpec("prime256v1"); + kpg.initialize(ecParamSpec); + + return kpg.generateKeyPair(); + } + + private static byte[] generateSecret(PrivateKey myPrivKey, PublicKey otherPubKey) throws Exception { + ECPublicKey ecPubKey = (ECPublicKey) otherPubKey; + KeyAgreement keyAgreement = KeyAgreement.getInstance(KEGEN_ALG, PROVIDER); + keyAgreement.init(myPrivKey); + keyAgreement.doPhase(otherPubKey, true); + + return keyAgreement.generateSecret(); + } + + + static synchronized KeyPair readKeyPair(Context context) + throws Exception { + return new KeyPair(readMyPublicKey(context), readMyPrivateKey(context)); + } + + @SuppressLint("ApplySharedPref") + public static KeyPair newPair(Context context) { + SharedPreferences.Editor prefsEditor = PreferenceManager + .getDefaultSharedPreferences(context).edit(); + KeyPair kp; + try { + kp = generateKeyPair(); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + + ECPublicKey key = (ECPublicKey) kp.getPublic(); + byte[] x = key.getW().getAffineX().toByteArray(); + byte[] y = key.getW().getAffineY().toByteArray(); + BigInteger xbi = new BigInteger(1, x); + BigInteger ybi = new BigInteger(1, y); + X9ECParameters x9 = ECNamedCurveTable.getByName("prime256v1"); + ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID("prime256v1"); + + ECCurve curve = x9.getCurve(); + ECPoint point = curve.createPoint(xbi, ybi); + ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid, + x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed()); + + ECPublicKeyParameters pubKey = new ECPublicKeyParameters(point, dParams); + + + ECPrivateKeyParameters privateKey = new ECPrivateKeyParameters(new BigInteger(kp.getPrivate().getEncoded()), pubKey.getParameters()); + byte[] privateKeyBytes = privateKey.getD().toByteArray(); + + String keyString = base64Encode(pubKey.getQ().getEncoded(false)); + String keypString = base64Encode(privateKeyBytes); + prefsEditor.putString(kp_public, keyString); + prefsEditor.putString(kp_public_affine_x, key.getW().getAffineX().toString()); + prefsEditor.putString(kp_public_affine_y, key.getW().getAffineY().toString()); + prefsEditor.putString(kp_private, keypString); + prefsEditor.commit(); + return kp; + } + + + static synchronized PublicKey readMyPublicKey(Context context) throws Exception { + + X9ECParameters x9 = ECNamedCurveTable.getByName("prime256v1"); + ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID("prime256v1"); + + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); + BigInteger xbi = new BigInteger(prefs.getString(kp_public_affine_x, "0")); + BigInteger ybi = new BigInteger(prefs.getString(kp_public_affine_y, "0")); + + ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid, + x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed()); + + + ECNamedCurveSpec ecNamedCurveSpec = new ECNamedCurveSpec("prime256v1", dParams.getCurve(), dParams.getG(), dParams.getN()); + java.security.spec.ECPoint w = new java.security.spec.ECPoint(xbi, ybi); + return kf.generatePublic(new java.security.spec.ECPublicKeySpec(w, ecNamedCurveSpec)); + } + + + static synchronized PublicKey readPublicKey(String keyStr) throws Exception { + X509EncodedKeySpec x509ks = new X509EncodedKeySpec( + base64Decode(keyStr)); + return kf.generatePublic(x509ks); + } + + + static synchronized PrivateKey readMyPrivateKey(Context context) throws Exception { + X9ECParameters x9 = ECNamedCurveTable.getByName("prime256v1"); + ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID("prime256v1"); + + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); + BigInteger ybi = new BigInteger(prefs.getString(kp_public_affine_y, "0")); + ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid, + x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed()); + ECNamedCurveSpec ecNamedCurveSpec = new ECNamedCurveSpec("prime256v1", dParams.getCurve(), dParams.getG(), dParams.getN()); + return kf.generatePrivate(new java.security.spec.ECPrivateKeySpec(ybi, ecNamedCurveSpec)); + } + + + private static synchronized KeyPair getPair(Context context) { + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); + String strPub = prefs.getString(kp_public, ""); + String strPriv = prefs.getString(kp_private, ""); + if (strPub.trim().isEmpty() || strPriv.trim().isEmpty()) { + return newPair(context); + } + try { + return readKeyPair(context); + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + static PublicKey getServerKey(Context context, Account account) throws Exception { + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); + String serverKey = prefs.getString(peer_public + account.getId() + account.getInstance(), ""); + return readPublicKey(serverKey); + } + + @SuppressWarnings({"unused", "RedundantSuppression"}) + public static byte[] getSharedSecret(Context context, Account account) { + try { + return generateSecret(getPair(context).getPrivate(), getServerKey(context, account)); + } catch (Exception e) { + e.printStackTrace(); + return null; + } + } + + public String getPublicKey(Context context) { + SharedPreferences prefs = PreferenceManager + .getDefaultSharedPreferences(context); + + return prefs.getString(kp_public, ""); + } + + @SuppressLint("ApplySharedPref") + public void saveServerKey(Context context, Account account, String strPeerPublic) { + SharedPreferences.Editor prefsEditor = PreferenceManager + .getDefaultSharedPreferences(context).edit(); + + prefsEditor.putString(peer_public + account.getId() + account.getInstance(), strPeerPublic); + prefsEditor.commit(); + } +} From a143c61e3d0ccdc749677d478e6f041c8def4aa4 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 1 Mar 2021 12:11:07 +0100 Subject: [PATCH 24/35] copy release notes --- .../android/services/UnifiedPushService.java | 0 .../android/services/UnifiedPushService.java | 91 +++++++++++++++++++ 2 files changed, 91 insertions(+) rename app/src/{main => fdroidcommon}/java/app/fedilab/android/services/UnifiedPushService.java (100%) create mode 100644 app/src/playstore/java/app/fedilab/android/services/UnifiedPushService.java diff --git a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java b/app/src/fdroidcommon/java/app/fedilab/android/services/UnifiedPushService.java similarity index 100% rename from app/src/main/java/app/fedilab/android/services/UnifiedPushService.java rename to app/src/fdroidcommon/java/app/fedilab/android/services/UnifiedPushService.java diff --git a/app/src/playstore/java/app/fedilab/android/services/UnifiedPushService.java b/app/src/playstore/java/app/fedilab/android/services/UnifiedPushService.java new file mode 100644 index 000000000..cf63316d5 --- /dev/null +++ b/app/src/playstore/java/app/fedilab/android/services/UnifiedPushService.java @@ -0,0 +1,91 @@ +package app.fedilab.android.services; +/* Copyright 2021 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ +import android.content.Context; +import android.content.SharedPreferences; +import android.database.sqlite.SQLiteDatabase; +import android.util.Log; + +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.unifiedpush.android.connector.MessagingReceiver; +import org.unifiedpush.android.connector.MessagingReceiverHandler; + + +import app.fedilab.android.client.API; +import app.fedilab.android.client.APIResponse; +import app.fedilab.android.client.Entities.Account; +import app.fedilab.android.client.GNUAPI; +import app.fedilab.android.fragments.DisplayNotificationsFragment; +import app.fedilab.android.helper.Helper; +import app.fedilab.android.helper.NotificationsHelper; +import app.fedilab.android.helper.PushNotifications; +import app.fedilab.android.sqlite.AccountDAO; +import app.fedilab.android.sqlite.Sqlite; + +import static android.content.Context.MODE_PRIVATE; + +class handler implements MessagingReceiverHandler { + + + @Override + public void onMessage(@Nullable Context context, @NotNull String s, @NotNull String slug) { + new Thread(() -> { + SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + String[] slugArray = slug.split("@"); + Account account = new AccountDAO(context, db).getUniqAccountUsernameInstance(slugArray[0], slugArray[1]); + APIResponse apiResponse; + if (account.getSocial().compareTo("FRIENDICA") != 0 && account.getSocial().compareTo("GNU") != 0) { + API api = new API(context, account.getInstance(), account.getToken()); + apiResponse = api.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, null, false); + } else { + GNUAPI gnuApi = new GNUAPI(context, account.getInstance(), account.getToken()); + apiResponse = gnuApi.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, null); + } + NotificationsHelper.onRetrieveNotifications(context, apiResponse, account); + }).start(); + } + + @Override + public void onNewEndpoint(@Nullable Context context, @NotNull String endpoint, @NotNull String slug) { + final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(Helper.SERVER_ENDPOINT, endpoint); + editor.apply(); + new PushNotifications() + .registerPushNotifications(context, endpoint, slug); + } + + @Override + public void onRegistrationFailed(@Nullable Context context, @NotNull String s) { + + } + + @Override + public void onRegistrationRefused(@Nullable Context context, @NotNull String s) { + + } + + @Override + public void onUnregistered(@Nullable Context context, @NotNull String s) { + + } +} + +public class UnifiedPushService extends MessagingReceiver { + public UnifiedPushService() { + super(new handler()); + } +} From 37b007eea576edafc119e30462a49653b557d115 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 1 Mar 2021 12:13:55 +0100 Subject: [PATCH 25/35] copy release notes --- .../java/app/fedilab/android/helper/PushHelper.java | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 app/src/fdroidcommon/java/app/fedilab/android/helper/PushHelper.java diff --git a/app/src/fdroidcommon/java/app/fedilab/android/helper/PushHelper.java b/app/src/fdroidcommon/java/app/fedilab/android/helper/PushHelper.java new file mode 100644 index 000000000..ba8a7fbc0 --- /dev/null +++ b/app/src/fdroidcommon/java/app/fedilab/android/helper/PushHelper.java @@ -0,0 +1,4 @@ +package app.fedilab.android.helper; + +class PushHelper { +} From dc85e4e156d4bcc131d338f3cd5dd433295115c8 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 1 Mar 2021 12:15:30 +0100 Subject: [PATCH 26/35] copy release notes --- .../fedilab/android/helper/PushHelper.java | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 app/src/playstore/java/app/fedilab/android/helper/PushHelper.java diff --git a/app/src/playstore/java/app/fedilab/android/helper/PushHelper.java b/app/src/playstore/java/app/fedilab/android/helper/PushHelper.java new file mode 100644 index 000000000..f0760c53f --- /dev/null +++ b/app/src/playstore/java/app/fedilab/android/helper/PushHelper.java @@ -0,0 +1,44 @@ +package app.fedilab.android.helper; + +import android.app.Activity; +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; + +import org.unifiedpush.android.connector.Registration; + +import java.util.List; + +import app.fedilab.android.client.Entities.Account; +import app.fedilab.android.jobs.ApplicationJob; +import app.fedilab.android.jobs.NotificationsSyncJob; +import app.fedilab.android.sqlite.AccountDAO; +import app.fedilab.android.sqlite.Sqlite; + +import static app.fedilab.android.helper.BaseHelper.NOTIF_NONE; +import static app.fedilab.android.helper.BaseHelper.NOTIF_PUSH; +import static app.fedilab.android.helper.BaseHelper.liveNotifType; + +public class PushHelper { + + public static void startStreaming(Context context) { + int liveNotifications = liveNotifType(context); + ApplicationJob.cancelAllJob(NotificationsSyncJob.NOTIFICATION_REFRESH); + NotificationsSyncJob.schedule(false); + switch (liveNotifications) { + case NOTIF_PUSH: + new Thread(() -> { + SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + List accounts = new AccountDAO(context, db).getPushNotificationAccounts(); + ((Activity) context).runOnUiThread(() -> { + for (Account account : accounts) { + new Registration().registerAppWithDialog(context, account.getUsername() + "@" + account.getInstance()); + } + }); + }).start(); + break; + case NOTIF_NONE: + new Registration().unregisterApp(context); + break; + } + } +} From 5f026bc45b1a5406a97954cd43131676176199c9 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 1 Mar 2021 14:10:56 +0100 Subject: [PATCH 27/35] copy release notes --- app/src/fdroidcommon/AndroidManifest.xml | 103 ++++++++++++++++++ app/src/playstore/AndroidManifest.xml | 37 +++++++ .../android/services/UnifiedPushService.java | 55 +--------- 3 files changed, 142 insertions(+), 53 deletions(-) create mode 100644 app/src/fdroidcommon/AndroidManifest.xml create mode 100644 app/src/playstore/AndroidManifest.xml diff --git a/app/src/fdroidcommon/AndroidManifest.xml b/app/src/fdroidcommon/AndroidManifest.xml new file mode 100644 index 000000000..397a7c259 --- /dev/null +++ b/app/src/fdroidcommon/AndroidManifest.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/playstore/AndroidManifest.xml b/app/src/playstore/AndroidManifest.xml new file mode 100644 index 000000000..68246105c --- /dev/null +++ b/app/src/playstore/AndroidManifest.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/playstore/java/app/fedilab/android/services/UnifiedPushService.java b/app/src/playstore/java/app/fedilab/android/services/UnifiedPushService.java index cf63316d5..07e27658a 100644 --- a/app/src/playstore/java/app/fedilab/android/services/UnifiedPushService.java +++ b/app/src/playstore/java/app/fedilab/android/services/UnifiedPushService.java @@ -13,16 +13,13 @@ package app.fedilab.android.services; * * You should have received a copy of the GNU General Public License along with Fedilab; if not, * see . */ + import android.content.Context; import android.content.SharedPreferences; import android.database.sqlite.SQLiteDatabase; -import android.util.Log; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.unifiedpush.android.connector.MessagingReceiver; -import org.unifiedpush.android.connector.MessagingReceiverHandler; - import app.fedilab.android.client.API; import app.fedilab.android.client.APIResponse; @@ -37,55 +34,7 @@ import app.fedilab.android.sqlite.Sqlite; import static android.content.Context.MODE_PRIVATE; -class handler implements MessagingReceiverHandler { +class handler { - @Override - public void onMessage(@Nullable Context context, @NotNull String s, @NotNull String slug) { - new Thread(() -> { - SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); - String[] slugArray = slug.split("@"); - Account account = new AccountDAO(context, db).getUniqAccountUsernameInstance(slugArray[0], slugArray[1]); - APIResponse apiResponse; - if (account.getSocial().compareTo("FRIENDICA") != 0 && account.getSocial().compareTo("GNU") != 0) { - API api = new API(context, account.getInstance(), account.getToken()); - apiResponse = api.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, null, false); - } else { - GNUAPI gnuApi = new GNUAPI(context, account.getInstance(), account.getToken()); - apiResponse = gnuApi.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, null); - } - NotificationsHelper.onRetrieveNotifications(context, apiResponse, account); - }).start(); - } - - @Override - public void onNewEndpoint(@Nullable Context context, @NotNull String endpoint, @NotNull String slug) { - final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); - SharedPreferences.Editor editor = sharedpreferences.edit(); - editor.putString(Helper.SERVER_ENDPOINT, endpoint); - editor.apply(); - new PushNotifications() - .registerPushNotifications(context, endpoint, slug); - } - - @Override - public void onRegistrationFailed(@Nullable Context context, @NotNull String s) { - - } - - @Override - public void onRegistrationRefused(@Nullable Context context, @NotNull String s) { - - } - - @Override - public void onUnregistered(@Nullable Context context, @NotNull String s) { - - } -} - -public class UnifiedPushService extends MessagingReceiver { - public UnifiedPushService() { - super(new handler()); - } } From 0350761cade7bad9229c05e3e73cd913506a8a75 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 1 Mar 2021 14:26:06 +0100 Subject: [PATCH 28/35] copy release notes --- .../android/services/UnifiedPushService.java | 0 .../android/services/CustomReceiver.java | 12 ++++++ .../fedilab/android/services/HandlerFCM.java | 16 ++++++++ .../android/services/UnifiedPushService.java | 40 ------------------- 4 files changed, 28 insertions(+), 40 deletions(-) rename app/src/{fdroidcommon => main}/java/app/fedilab/android/services/UnifiedPushService.java (100%) create mode 100644 app/src/playstore/java/app/fedilab/android/services/CustomReceiver.java create mode 100644 app/src/playstore/java/app/fedilab/android/services/HandlerFCM.java delete mode 100644 app/src/playstore/java/app/fedilab/android/services/UnifiedPushService.java diff --git a/app/src/fdroidcommon/java/app/fedilab/android/services/UnifiedPushService.java b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java similarity index 100% rename from app/src/fdroidcommon/java/app/fedilab/android/services/UnifiedPushService.java rename to app/src/main/java/app/fedilab/android/services/UnifiedPushService.java diff --git a/app/src/playstore/java/app/fedilab/android/services/CustomReceiver.java b/app/src/playstore/java/app/fedilab/android/services/CustomReceiver.java new file mode 100644 index 000000000..da7278ef5 --- /dev/null +++ b/app/src/playstore/java/app/fedilab/android/services/CustomReceiver.java @@ -0,0 +1,12 @@ +package app.fedilab.android.services; + +import org.jetbrains.annotations.NotNull; +import org.unifiedpush.android.connector_fcm_added.FakeDistributorReceiver; +import org.unifiedpush.android.connector_fcm_added.GetEndpointHandler; + +class CustomReceiver extends FakeDistributorReceiver { + public CustomReceiver() { + super(new handlerFCM()); + } + +} \ No newline at end of file diff --git a/app/src/playstore/java/app/fedilab/android/services/HandlerFCM.java b/app/src/playstore/java/app/fedilab/android/services/HandlerFCM.java new file mode 100644 index 000000000..100949c6a --- /dev/null +++ b/app/src/playstore/java/app/fedilab/android/services/HandlerFCM.java @@ -0,0 +1,16 @@ +package app.fedilab.android.services; + +import android.content.Context; + +import androidx.annotation.Nullable; + +import org.jetbrains.annotations.NotNull; +import org.unifiedpush.android.connector_fcm_added.GetEndpointHandler; + +public class HandlerFCM implements GetEndpointHandler { + + @Override + public @NotNull String getEndpoint(@Nullable Context context, @NotNull String token, @NotNull String instance) { + return "https://gotify.fedilab.org/UP?token=" + token; + } +} \ No newline at end of file diff --git a/app/src/playstore/java/app/fedilab/android/services/UnifiedPushService.java b/app/src/playstore/java/app/fedilab/android/services/UnifiedPushService.java deleted file mode 100644 index 07e27658a..000000000 --- a/app/src/playstore/java/app/fedilab/android/services/UnifiedPushService.java +++ /dev/null @@ -1,40 +0,0 @@ -package app.fedilab.android.services; -/* Copyright 2021 Thomas Schneider - * - * This file is a part of Fedilab - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Fedilab; if not, - * see . */ - -import android.content.Context; -import android.content.SharedPreferences; -import android.database.sqlite.SQLiteDatabase; - -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import app.fedilab.android.client.API; -import app.fedilab.android.client.APIResponse; -import app.fedilab.android.client.Entities.Account; -import app.fedilab.android.client.GNUAPI; -import app.fedilab.android.fragments.DisplayNotificationsFragment; -import app.fedilab.android.helper.Helper; -import app.fedilab.android.helper.NotificationsHelper; -import app.fedilab.android.helper.PushNotifications; -import app.fedilab.android.sqlite.AccountDAO; -import app.fedilab.android.sqlite.Sqlite; - -import static android.content.Context.MODE_PRIVATE; - -class handler { - - -} From 03b41c78b31d37512712b15e98aadab422d6e704 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 1 Mar 2021 14:49:36 +0100 Subject: [PATCH 29/35] copy release notes --- app/src/fdroidcommon/AndroidManifest.xml | 103 ------- .../java/app/fedilab/android/helper/ECDH.java | 2 - .../java/app/fedilab/android/helper/ECDH.java | 261 ------------------ .../android/services/CustomReceiver.java | 12 - .../android/services/FakeDistributor.java | 10 + 5 files changed, 10 insertions(+), 378 deletions(-) delete mode 100644 app/src/fdroidcommon/AndroidManifest.xml rename app/src/{fdroidcommon => main}/java/app/fedilab/android/helper/ECDH.java (99%) delete mode 100644 app/src/playstore/java/app/fedilab/android/helper/ECDH.java delete mode 100644 app/src/playstore/java/app/fedilab/android/services/CustomReceiver.java create mode 100644 app/src/playstore/java/app/fedilab/android/services/FakeDistributor.java diff --git a/app/src/fdroidcommon/AndroidManifest.xml b/app/src/fdroidcommon/AndroidManifest.xml deleted file mode 100644 index 397a7c259..000000000 --- a/app/src/fdroidcommon/AndroidManifest.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/fdroidcommon/java/app/fedilab/android/helper/ECDH.java b/app/src/main/java/app/fedilab/android/helper/ECDH.java similarity index 99% rename from app/src/fdroidcommon/java/app/fedilab/android/helper/ECDH.java rename to app/src/main/java/app/fedilab/android/helper/ECDH.java index 674b48188..bf382a8a9 100644 --- a/app/src/fdroidcommon/java/app/fedilab/android/helper/ECDH.java +++ b/app/src/main/java/app/fedilab/android/helper/ECDH.java @@ -18,11 +18,9 @@ import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; import android.util.Base64; -import android.util.Log; import androidx.preference.PreferenceManager; - import org.spongycastle.asn1.ASN1ObjectIdentifier; import org.spongycastle.asn1.x9.ECNamedCurveTable; import org.spongycastle.asn1.x9.X9ECParameters; diff --git a/app/src/playstore/java/app/fedilab/android/helper/ECDH.java b/app/src/playstore/java/app/fedilab/android/helper/ECDH.java deleted file mode 100644 index 674b48188..000000000 --- a/app/src/playstore/java/app/fedilab/android/helper/ECDH.java +++ /dev/null @@ -1,261 +0,0 @@ -package app.fedilab.android.helper; -/* Copyright 2021 Thomas Schneider - * - * This file is a part of Fedilab - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Fedilab; if not, - * see . */ - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.SharedPreferences; -import android.util.Base64; -import android.util.Log; - -import androidx.preference.PreferenceManager; - - -import org.spongycastle.asn1.ASN1ObjectIdentifier; -import org.spongycastle.asn1.x9.ECNamedCurveTable; -import org.spongycastle.asn1.x9.X9ECParameters; -import org.spongycastle.crypto.params.ECNamedDomainParameters; -import org.spongycastle.crypto.params.ECPrivateKeyParameters; -import org.spongycastle.crypto.params.ECPublicKeyParameters; -import org.spongycastle.jce.spec.ECNamedCurveSpec; -import org.spongycastle.math.ec.ECCurve; -import org.spongycastle.math.ec.ECPoint; - -import java.math.BigInteger; -import java.security.KeyFactory; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.security.Security; -import java.security.interfaces.ECPublicKey; -import java.security.spec.ECGenParameterSpec; -import java.security.spec.X509EncodedKeySpec; - -import javax.crypto.KeyAgreement; - -import app.fedilab.android.client.Entities.Account; - -// https://github.com/nelenkov/ecdh-kx/blob/master/src/org/nick/ecdhkx/Crypto.java - -public class ECDH { - - - public static final String kp_public = "kp_public"; - public static final String peer_public = "peer_public"; - public static final String PROVIDER = org.spongycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME; - - public static final String kp_private = "kp_private"; - public static final String KEGEN_ALG = "ECDH"; - - private static final String kp_public_affine_x = "kp_public_affine_x"; - private static final String kp_public_affine_y = "kp_public_affine_y"; - - private static ECDH instance; - - - static { - Security.addProvider(new org.spongycastle.jce.provider.BouncyCastleProvider()); - } - - public static KeyFactory kf = null; - private static KeyPairGenerator kpg = null; - - - public ECDH() { - try { - kf = KeyFactory.getInstance(KEGEN_ALG, PROVIDER); - kpg = KeyPairGenerator.getInstance(KEGEN_ALG, PROVIDER); - } catch (NoSuchAlgorithmException | NoSuchProviderException e) { - throw new RuntimeException(e); - } - } - - public static synchronized ECDH getInstance() { - if (instance == null) { - instance = new ECDH(); - } - - return instance; - } - - public static String base64Encode(byte[] b) { - - byte[] encoded = Base64.encode( - b, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP); - return new String(encoded); - } - - static byte[] base64Decode(String str) { - return Base64.decode(str, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP); - } - - static synchronized KeyPair generateKeyPair() - throws Exception { - ECGenParameterSpec ecParamSpec = new ECGenParameterSpec("prime256v1"); - kpg.initialize(ecParamSpec); - - return kpg.generateKeyPair(); - } - - private static byte[] generateSecret(PrivateKey myPrivKey, PublicKey otherPubKey) throws Exception { - ECPublicKey ecPubKey = (ECPublicKey) otherPubKey; - KeyAgreement keyAgreement = KeyAgreement.getInstance(KEGEN_ALG, PROVIDER); - keyAgreement.init(myPrivKey); - keyAgreement.doPhase(otherPubKey, true); - - return keyAgreement.generateSecret(); - } - - - static synchronized KeyPair readKeyPair(Context context) - throws Exception { - return new KeyPair(readMyPublicKey(context), readMyPrivateKey(context)); - } - - @SuppressLint("ApplySharedPref") - public static KeyPair newPair(Context context) { - SharedPreferences.Editor prefsEditor = PreferenceManager - .getDefaultSharedPreferences(context).edit(); - KeyPair kp; - try { - kp = generateKeyPair(); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - - ECPublicKey key = (ECPublicKey) kp.getPublic(); - byte[] x = key.getW().getAffineX().toByteArray(); - byte[] y = key.getW().getAffineY().toByteArray(); - BigInteger xbi = new BigInteger(1, x); - BigInteger ybi = new BigInteger(1, y); - X9ECParameters x9 = ECNamedCurveTable.getByName("prime256v1"); - ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID("prime256v1"); - - ECCurve curve = x9.getCurve(); - ECPoint point = curve.createPoint(xbi, ybi); - ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid, - x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed()); - - ECPublicKeyParameters pubKey = new ECPublicKeyParameters(point, dParams); - - - ECPrivateKeyParameters privateKey = new ECPrivateKeyParameters(new BigInteger(kp.getPrivate().getEncoded()), pubKey.getParameters()); - byte[] privateKeyBytes = privateKey.getD().toByteArray(); - - String keyString = base64Encode(pubKey.getQ().getEncoded(false)); - String keypString = base64Encode(privateKeyBytes); - prefsEditor.putString(kp_public, keyString); - prefsEditor.putString(kp_public_affine_x, key.getW().getAffineX().toString()); - prefsEditor.putString(kp_public_affine_y, key.getW().getAffineY().toString()); - prefsEditor.putString(kp_private, keypString); - prefsEditor.commit(); - return kp; - } - - - static synchronized PublicKey readMyPublicKey(Context context) throws Exception { - - X9ECParameters x9 = ECNamedCurveTable.getByName("prime256v1"); - ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID("prime256v1"); - - SharedPreferences prefs = PreferenceManager - .getDefaultSharedPreferences(context); - BigInteger xbi = new BigInteger(prefs.getString(kp_public_affine_x, "0")); - BigInteger ybi = new BigInteger(prefs.getString(kp_public_affine_y, "0")); - - ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid, - x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed()); - - - ECNamedCurveSpec ecNamedCurveSpec = new ECNamedCurveSpec("prime256v1", dParams.getCurve(), dParams.getG(), dParams.getN()); - java.security.spec.ECPoint w = new java.security.spec.ECPoint(xbi, ybi); - return kf.generatePublic(new java.security.spec.ECPublicKeySpec(w, ecNamedCurveSpec)); - } - - - static synchronized PublicKey readPublicKey(String keyStr) throws Exception { - X509EncodedKeySpec x509ks = new X509EncodedKeySpec( - base64Decode(keyStr)); - return kf.generatePublic(x509ks); - } - - - static synchronized PrivateKey readMyPrivateKey(Context context) throws Exception { - X9ECParameters x9 = ECNamedCurveTable.getByName("prime256v1"); - ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID("prime256v1"); - - SharedPreferences prefs = PreferenceManager - .getDefaultSharedPreferences(context); - BigInteger ybi = new BigInteger(prefs.getString(kp_public_affine_y, "0")); - ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid, - x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed()); - ECNamedCurveSpec ecNamedCurveSpec = new ECNamedCurveSpec("prime256v1", dParams.getCurve(), dParams.getG(), dParams.getN()); - return kf.generatePrivate(new java.security.spec.ECPrivateKeySpec(ybi, ecNamedCurveSpec)); - } - - - private static synchronized KeyPair getPair(Context context) { - SharedPreferences prefs = PreferenceManager - .getDefaultSharedPreferences(context); - String strPub = prefs.getString(kp_public, ""); - String strPriv = prefs.getString(kp_private, ""); - if (strPub.trim().isEmpty() || strPriv.trim().isEmpty()) { - return newPair(context); - } - try { - return readKeyPair(context); - } catch (Exception e) { - e.printStackTrace(); - } - return null; - } - - static PublicKey getServerKey(Context context, Account account) throws Exception { - SharedPreferences prefs = PreferenceManager - .getDefaultSharedPreferences(context); - String serverKey = prefs.getString(peer_public + account.getId() + account.getInstance(), ""); - return readPublicKey(serverKey); - } - - @SuppressWarnings({"unused", "RedundantSuppression"}) - public static byte[] getSharedSecret(Context context, Account account) { - try { - return generateSecret(getPair(context).getPrivate(), getServerKey(context, account)); - } catch (Exception e) { - e.printStackTrace(); - return null; - } - } - - public String getPublicKey(Context context) { - SharedPreferences prefs = PreferenceManager - .getDefaultSharedPreferences(context); - - return prefs.getString(kp_public, ""); - } - - @SuppressLint("ApplySharedPref") - public void saveServerKey(Context context, Account account, String strPeerPublic) { - SharedPreferences.Editor prefsEditor = PreferenceManager - .getDefaultSharedPreferences(context).edit(); - - prefsEditor.putString(peer_public + account.getId() + account.getInstance(), strPeerPublic); - prefsEditor.commit(); - } -} diff --git a/app/src/playstore/java/app/fedilab/android/services/CustomReceiver.java b/app/src/playstore/java/app/fedilab/android/services/CustomReceiver.java deleted file mode 100644 index da7278ef5..000000000 --- a/app/src/playstore/java/app/fedilab/android/services/CustomReceiver.java +++ /dev/null @@ -1,12 +0,0 @@ -package app.fedilab.android.services; - -import org.jetbrains.annotations.NotNull; -import org.unifiedpush.android.connector_fcm_added.FakeDistributorReceiver; -import org.unifiedpush.android.connector_fcm_added.GetEndpointHandler; - -class CustomReceiver extends FakeDistributorReceiver { - public CustomReceiver() { - super(new handlerFCM()); - } - -} \ No newline at end of file diff --git a/app/src/playstore/java/app/fedilab/android/services/FakeDistributor.java b/app/src/playstore/java/app/fedilab/android/services/FakeDistributor.java new file mode 100644 index 000000000..7e8d88f14 --- /dev/null +++ b/app/src/playstore/java/app/fedilab/android/services/FakeDistributor.java @@ -0,0 +1,10 @@ +package app.fedilab.android.services; + +import org.unifiedpush.android.connector_fcm_added.FakeDistributorReceiver; + +class FakeDistributor extends FakeDistributorReceiver { + public FakeDistributor() { + super(new HandlerFCM()); + } + +} \ No newline at end of file From cebce4bec65a4574fd9356a97aad83e0e939e50c Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 1 Mar 2021 18:19:08 +0100 Subject: [PATCH 30/35] google fcm --- app/build.gradle | 20 +++++---- .../fedilab/android/helper/PushHelper.java | 42 ++++++++++++++++++- .../android/activities/BaseMainActivity.java | 7 +++- .../asynctasks/PostStatusAsyncTask.java | 6 +-- .../java/app/fedilab/android/client/API.java | 3 -- .../fragments/ContentSettingsFragment.java | 5 ++- .../fedilab/android/helper/BaseHelper.java | 22 ---------- .../android/services/UnifiedPushService.java | 13 ++---- app/src/playstore/AndroidManifest.xml | 12 ++---- .../fedilab/android/helper/PushHelper.java | 13 ++++-- .../android/services/FakeDistributor.java | 2 +- .../fedilab/android/services/HandlerFCM.java | 2 + build.gradle | 1 + 13 files changed, 85 insertions(+), 63 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index ae793a0c4..6b0780f48 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,5 @@ apply plugin: 'com.android.application' - +def flavor android { compileSdkVersion 29 buildToolsVersion "29.0.3" @@ -42,6 +42,7 @@ android { buildConfigField "boolean", "DONATIONS", "true" buildConfigField "boolean", "lite", "false" resValue "string", "app_name", "Fedilab" + flavor = "fdroid" } lite { minSdkVersion 21 @@ -49,12 +50,14 @@ android { buildConfigField "boolean", "DONATIONS", "true" buildConfigField "boolean", "lite", "true" resValue "string", "app_name", "Fedilab Lite" + flavor = "lite" } playstore { applicationId "app.fedilab.android" buildConfigField "boolean", "DONATIONS", "false" buildConfigField "boolean", "lite", "false" resValue "string", "app_name", "Fedilab" + flavor = "playstore" } } buildFeatures { @@ -62,6 +65,7 @@ android { } sourceSets { playstore { + manifest.srcFile "src/common/AndroidManifest.xml" assets.srcDirs = ['/src/mains/assets', 'src/common/assets'] java.srcDirs = ['src/main/java', 'src/playstore/java','src/common/java'] @@ -71,13 +75,13 @@ android { fdroid { manifest.srcFile "src/common/AndroidManifest.xml" assets.srcDirs = ['/src/mains/assets', 'src/common/assets'] - java.srcDirs = ['src/main/java', 'src/fdroid/java','src/common/java'] + java.srcDirs = ['src/main/java', 'src/fdroid/java','src/common/java', 'src/fdroidcommon/java'] res.srcDirs = ['src/main/res', 'src/fdroid/res','src/common/res'] } lite { manifest.srcFile "src/lite/AndroidManifest.xml" assets.srcDirs = ['/src/mains/assets'] - java.srcDirs = ['src/main/java', 'src/lite/java'] + java.srcDirs = ['src/main/java', 'src/lite/java', 'src/fdroidcommon/java'] res.srcDirs = ['src/main/res', 'src/lite/res'] } } @@ -174,10 +178,8 @@ dependencies { implementation 'com.huangyz0918:androidwm-light:0.1.2' + implementation 'com.github.UnifiedPush:android-connector:dev-SNAPSHOT' implementation "com.madgag.spongycastle:bctls-jdk15on:1.58.0.0" - //implementation 'org.bouncycastle:bcprov-jdk15on:1.64' - - implementation 'com.github.p1gp1g:android-connector-1:1.1.0-beta1' //Flavors //Playstore @@ -187,7 +189,7 @@ dependencies { playstoreImplementation 'org.framagit.tom79:country-picker-android:1.2.0' playstoreImplementation 'com.vanniktech:emoji-one:0.6.0' playstoreImplementation 'ja.burhanrashid52:photoeditor:0.4.0' - + playstoreImplementation 'com.github.UnifiedPush:android-connector_fcm_added:dev-SNAPSHOT' //Fdroid fdroidApi 'com.theartofdev.edmodo:android-image-cropper:2.8.+' @@ -196,4 +198,8 @@ dependencies { fdroidImplementation 'com.vanniktech:emoji-one:0.6.0' fdroidImplementation 'ja.burhanrashid52:photoeditor:0.4.0' + + } + +apply plugin: 'com.google.gms.google-services' diff --git a/app/src/fdroidcommon/java/app/fedilab/android/helper/PushHelper.java b/app/src/fdroidcommon/java/app/fedilab/android/helper/PushHelper.java index ba8a7fbc0..f0760c53f 100644 --- a/app/src/fdroidcommon/java/app/fedilab/android/helper/PushHelper.java +++ b/app/src/fdroidcommon/java/app/fedilab/android/helper/PushHelper.java @@ -1,4 +1,44 @@ package app.fedilab.android.helper; -class PushHelper { +import android.app.Activity; +import android.content.Context; +import android.database.sqlite.SQLiteDatabase; + +import org.unifiedpush.android.connector.Registration; + +import java.util.List; + +import app.fedilab.android.client.Entities.Account; +import app.fedilab.android.jobs.ApplicationJob; +import app.fedilab.android.jobs.NotificationsSyncJob; +import app.fedilab.android.sqlite.AccountDAO; +import app.fedilab.android.sqlite.Sqlite; + +import static app.fedilab.android.helper.BaseHelper.NOTIF_NONE; +import static app.fedilab.android.helper.BaseHelper.NOTIF_PUSH; +import static app.fedilab.android.helper.BaseHelper.liveNotifType; + +public class PushHelper { + + public static void startStreaming(Context context) { + int liveNotifications = liveNotifType(context); + ApplicationJob.cancelAllJob(NotificationsSyncJob.NOTIFICATION_REFRESH); + NotificationsSyncJob.schedule(false); + switch (liveNotifications) { + case NOTIF_PUSH: + new Thread(() -> { + SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + List accounts = new AccountDAO(context, db).getPushNotificationAccounts(); + ((Activity) context).runOnUiThread(() -> { + for (Account account : accounts) { + new Registration().registerAppWithDialog(context, account.getUsername() + "@" + account.getInstance()); + } + }); + }).start(); + break; + case NOTIF_NONE: + new Registration().unregisterApp(context); + break; + } + } } diff --git a/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java index b4583a29f..9b3469253 100644 --- a/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java @@ -136,6 +136,7 @@ import app.fedilab.android.helper.CrossActions; import app.fedilab.android.helper.ExpandableHeightListView; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.MenuFloating; +import app.fedilab.android.helper.PushHelper; import app.fedilab.android.interfaces.OnFilterActionInterface; import app.fedilab.android.interfaces.OnRetrieveEmojiAccountInterface; import app.fedilab.android.interfaces.OnRetrieveFeedsInterface; @@ -308,6 +309,7 @@ public abstract class BaseMainActivity extends BaseActivity activity = this; rateThisApp(); + //Intialize Peertube information //This task will allow to instance a static PeertubeInformation class if (social == UpdateAccountInfoAsyncTask.SOCIAL.PEERTUBE) { @@ -666,7 +668,7 @@ public abstract class BaseMainActivity extends BaseActivity } boolean popupShown = sharedpreferences.getBoolean(Helper.SET_POPUP_PUSH, false); if (popupShown) { - Helper.startStreaming(BaseMainActivity.this); + PushHelper.startStreaming(BaseMainActivity.this); } if (hidde_menu != null) @@ -1224,7 +1226,7 @@ public abstract class BaseMainActivity extends BaseActivity case 0: editor.putBoolean(Helper.SET_PUSH_NOTIFICATIONS, true); editor.apply(); - Helper.startStreaming(BaseMainActivity.this); + PushHelper.startStreaming(BaseMainActivity.this); set_live_type_indication.setText(R.string.set_push_notifications); break; case 1: @@ -1371,6 +1373,7 @@ public abstract class BaseMainActivity extends BaseActivity protected abstract void rateThisApp(); + @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); diff --git a/app/src/main/java/app/fedilab/android/asynctasks/PostStatusAsyncTask.java b/app/src/main/java/app/fedilab/android/asynctasks/PostStatusAsyncTask.java index b4ba9117d..0ef2e3922 100644 --- a/app/src/main/java/app/fedilab/android/asynctasks/PostStatusAsyncTask.java +++ b/app/src/main/java/app/fedilab/android/asynctasks/PostStatusAsyncTask.java @@ -30,6 +30,7 @@ import app.fedilab.android.client.Entities.Account; import app.fedilab.android.client.Entities.Error; import app.fedilab.android.client.GNUAPI; import app.fedilab.android.helper.Helper; +import app.fedilab.android.helper.PushHelper; import app.fedilab.android.interfaces.OnPostStatusActionInterface; import app.fedilab.android.sqlite.Sqlite; import app.fedilab.android.sqlite.TagsCacheDAO; @@ -125,11 +126,6 @@ public class PostStatusAsyncTask { } }; thread.start(); - if (account != null) { - String key = account.getUsername() + "@" + account.getInstance(); - Helper.sleeps.put(key, 30000); - Helper.startStreaming(contextReference.get()); - } }; mainHandler.post(myRunnable); }).start(); diff --git a/app/src/main/java/app/fedilab/android/client/API.java b/app/src/main/java/app/fedilab/android/client/API.java index 460176393..695d40645 100644 --- a/app/src/main/java/app/fedilab/android/client/API.java +++ b/app/src/main/java/app/fedilab/android/client/API.java @@ -22,7 +22,6 @@ import android.os.Build; import android.os.Bundle; import android.text.Html; import android.text.SpannableString; -import android.util.Log; import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.preference.PreferenceManager; @@ -49,7 +48,6 @@ import java.net.URLEncoder; import java.net.UnknownHostException; import java.security.KeyManagementException; import java.security.NoSuchAlgorithmException; -import java.security.Security; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -107,7 +105,6 @@ import app.fedilab.android.client.Entities.TrendsHistory; import app.fedilab.android.fragments.DisplayNotificationsFragment; import app.fedilab.android.helper.ECDH; import app.fedilab.android.helper.Helper; -import app.fedilab.android.helper.PushNotifications; import app.fedilab.android.sqlite.AccountDAO; import app.fedilab.android.sqlite.Sqlite; import app.fedilab.android.sqlite.TimelineCacheDAO; diff --git a/app/src/main/java/app/fedilab/android/fragments/ContentSettingsFragment.java b/app/src/main/java/app/fedilab/android/fragments/ContentSettingsFragment.java index b21617bbc..94a46a5f7 100644 --- a/app/src/main/java/app/fedilab/android/fragments/ContentSettingsFragment.java +++ b/app/src/main/java/app/fedilab/android/fragments/ContentSettingsFragment.java @@ -84,6 +84,7 @@ import app.fedilab.android.drawers.AccountSearchDevAdapter; import app.fedilab.android.filelister.FileListerDialog; import app.fedilab.android.helper.ExpandableHeightListView; import app.fedilab.android.helper.Helper; +import app.fedilab.android.helper.PushHelper; import app.fedilab.android.interfaces.OnRetrieveRelationshipInterface; import app.fedilab.android.interfaces.OnRetrieveRemoteAccountInterface; import app.fedilab.android.services.DownloadTrackingDBScriptsService; @@ -926,7 +927,7 @@ public class ContentSettingsFragment extends Fragment implements OnRetrieveRemot } else { notification_container.setVisibility(View.GONE); } - Helper.startStreaming(context); + PushHelper.startStreaming(context); }); @@ -965,7 +966,7 @@ public class ContentSettingsFragment extends Fragment implements OnRetrieveRemot set_live_type_indication.setText(R.string.no_live_indication); break; } - Helper.startStreaming(context); + PushHelper.startStreaming(context); } else { liveNotificationCount++; } diff --git a/app/src/main/java/app/fedilab/android/helper/BaseHelper.java b/app/src/main/java/app/fedilab/android/helper/BaseHelper.java index 94583aca5..a7d59ebfd 100644 --- a/app/src/main/java/app/fedilab/android/helper/BaseHelper.java +++ b/app/src/main/java/app/fedilab/android/helper/BaseHelper.java @@ -137,7 +137,6 @@ import net.gotev.uploadservice.UploadService; import net.gotev.uploadservice.okhttp.OkHttpStack; import org.jetbrains.annotations.NotNull; -import org.unifiedpush.android.connector.Registration; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -597,27 +596,6 @@ public class BaseHelper { } } - public static void startStreaming(Context context) { - int liveNotifications = liveNotifType(context); - ApplicationJob.cancelAllJob(NotificationsSyncJob.NOTIFICATION_REFRESH); - NotificationsSyncJob.schedule(false); - switch (liveNotifications) { - case NOTIF_PUSH: - new Thread(() -> { - SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); - List accounts = new AccountDAO(context, db).getPushNotificationAccounts(); - ((Activity) context).runOnUiThread(() -> { - for (Account account : accounts) { - new Registration().registerAppWithDialog(context, account.getUsername() + "@" + account.getInstance()); - } - }); - }).start(); - break; - case NOTIF_NONE: - new Registration().unregisterApp(context); - break; - } - } /*** * Check if the user is connected to Internet diff --git a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java index cf63316d5..1f40dd643 100644 --- a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java +++ b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java @@ -14,7 +14,6 @@ package app.fedilab.android.services; * You should have received a copy of the GNU General Public License along with Fedilab; if not, * see . */ import android.content.Context; -import android.content.SharedPreferences; import android.database.sqlite.SQLiteDatabase; import android.util.Log; @@ -35,7 +34,6 @@ import app.fedilab.android.helper.PushNotifications; import app.fedilab.android.sqlite.AccountDAO; import app.fedilab.android.sqlite.Sqlite; -import static android.content.Context.MODE_PRIVATE; class handler implements MessagingReceiverHandler { @@ -60,27 +58,24 @@ class handler implements MessagingReceiverHandler { @Override public void onNewEndpoint(@Nullable Context context, @NotNull String endpoint, @NotNull String slug) { - final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); - SharedPreferences.Editor editor = sharedpreferences.edit(); - editor.putString(Helper.SERVER_ENDPOINT, endpoint); - editor.apply(); + Log.v(Helper.TAG, "onNewEndpoint: " + slug); new PushNotifications() .registerPushNotifications(context, endpoint, slug); } @Override public void onRegistrationFailed(@Nullable Context context, @NotNull String s) { - + Log.v(Helper.TAG, "onRegistrationFailed"); } @Override public void onRegistrationRefused(@Nullable Context context, @NotNull String s) { - + Log.v(Helper.TAG, "onRegistrationFailed"); } @Override public void onUnregistered(@Nullable Context context, @NotNull String s) { - + Log.v(Helper.TAG, "onRegistrationFailed"); } } diff --git a/app/src/playstore/AndroidManifest.xml b/app/src/playstore/AndroidManifest.xml index 68246105c..00e9f419d 100644 --- a/app/src/playstore/AndroidManifest.xml +++ b/app/src/playstore/AndroidManifest.xml @@ -21,17 +21,13 @@ + android:exported="false"> - - - - - + + - \ No newline at end of file diff --git a/app/src/playstore/java/app/fedilab/android/helper/PushHelper.java b/app/src/playstore/java/app/fedilab/android/helper/PushHelper.java index f0760c53f..db1a814e4 100644 --- a/app/src/playstore/java/app/fedilab/android/helper/PushHelper.java +++ b/app/src/playstore/java/app/fedilab/android/helper/PushHelper.java @@ -1,10 +1,14 @@ package app.fedilab.android.helper; import android.app.Activity; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.database.sqlite.SQLiteDatabase; +import android.util.Log; -import org.unifiedpush.android.connector.Registration; +import org.unifiedpush.android.connector_fcm_added.RegistrationFCM; import java.util.List; @@ -18,8 +22,10 @@ import static app.fedilab.android.helper.BaseHelper.NOTIF_NONE; import static app.fedilab.android.helper.BaseHelper.NOTIF_PUSH; import static app.fedilab.android.helper.BaseHelper.liveNotifType; + public class PushHelper { + public static void startStreaming(Context context) { int liveNotifications = liveNotifType(context); ApplicationJob.cancelAllJob(NotificationsSyncJob.NOTIFICATION_REFRESH); @@ -30,14 +36,15 @@ public class PushHelper { SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); List accounts = new AccountDAO(context, db).getPushNotificationAccounts(); ((Activity) context).runOnUiThread(() -> { + RegistrationFCM registration = new RegistrationFCM(); for (Account account : accounts) { - new Registration().registerAppWithDialog(context, account.getUsername() + "@" + account.getInstance()); + registration.registerAppWithDialog(context, account.getUsername() + "@" + account.getInstance()); } }); }).start(); break; case NOTIF_NONE: - new Registration().unregisterApp(context); + new RegistrationFCM().unregisterApp(context); break; } } diff --git a/app/src/playstore/java/app/fedilab/android/services/FakeDistributor.java b/app/src/playstore/java/app/fedilab/android/services/FakeDistributor.java index 7e8d88f14..460989e47 100644 --- a/app/src/playstore/java/app/fedilab/android/services/FakeDistributor.java +++ b/app/src/playstore/java/app/fedilab/android/services/FakeDistributor.java @@ -2,7 +2,7 @@ package app.fedilab.android.services; import org.unifiedpush.android.connector_fcm_added.FakeDistributorReceiver; -class FakeDistributor extends FakeDistributorReceiver { +public class FakeDistributor extends FakeDistributorReceiver { public FakeDistributor() { super(new HandlerFCM()); } diff --git a/app/src/playstore/java/app/fedilab/android/services/HandlerFCM.java b/app/src/playstore/java/app/fedilab/android/services/HandlerFCM.java index 100949c6a..92f32dad8 100644 --- a/app/src/playstore/java/app/fedilab/android/services/HandlerFCM.java +++ b/app/src/playstore/java/app/fedilab/android/services/HandlerFCM.java @@ -13,4 +13,6 @@ public class HandlerFCM implements GetEndpointHandler { public @NotNull String getEndpoint(@Nullable Context context, @NotNull String token, @NotNull String instance) { return "https://gotify.fedilab.org/UP?token=" + token; } + + } \ No newline at end of file diff --git a/build.gradle b/build.gradle index 318aa1c47..a83b27e67 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,7 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:4.1.2' + classpath 'com.google.gms:google-services:4.3.5' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files From 7102ff3c799d29e3e28b2ed3c088020db5142cb6 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 2 Mar 2021 11:59:49 +0100 Subject: [PATCH 31/35] custom popup --- app/build.gradle | 33 ++++++-- .../fedilab/android/helper/PushHelper.java | 61 +++++++++++++- .../android/activities/LoginActivity.java | 29 ++++--- .../java/app/fedilab/android/client/API.java | 4 +- .../drawers/BaseStatusListAdapter.java | 7 +- .../java/app/fedilab/android/helper/ECDH.java | 1 + .../android/helper/NotificationsHelper.java | 14 +++- .../jobs/BaseNotificationsSyncJob.java | 41 +-------- .../android/services/UnifiedPushService.java | 21 +---- app/src/main/res/values/strings.xml | 3 + app/src/playstore/AndroidManifest.xml | 84 ++++++++++++++++++- .../fedilab/android/services/HandlerFCM.java | 2 +- 12 files changed, 209 insertions(+), 91 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 6b0780f48..c7a4df47f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,3 +1,6 @@ +import java.util.regex.Matcher +import java.util.regex.Pattern + apply plugin: 'com.android.application' def flavor android { @@ -65,8 +68,7 @@ android { } sourceSets { playstore { - - manifest.srcFile "src/common/AndroidManifest.xml" + manifest.srcFile "src/playstore/AndroidManifest.xml" assets.srcDirs = ['/src/mains/assets', 'src/common/assets'] java.srcDirs = ['src/main/java', 'src/playstore/java','src/common/java'] res.srcDirs = ['src/main/res', 'src/playstore/res','src/common/res'] @@ -178,7 +180,7 @@ dependencies { implementation 'com.huangyz0918:androidwm-light:0.1.2' - implementation 'com.github.UnifiedPush:android-connector:dev-SNAPSHOT' + implementation 'com.github.UnifiedPush:android-connector:1.1.0' implementation "com.madgag.spongycastle:bctls-jdk15on:1.58.0.0" //Flavors @@ -189,7 +191,7 @@ dependencies { playstoreImplementation 'org.framagit.tom79:country-picker-android:1.2.0' playstoreImplementation 'com.vanniktech:emoji-one:0.6.0' playstoreImplementation 'ja.burhanrashid52:photoeditor:0.4.0' - playstoreImplementation 'com.github.UnifiedPush:android-connector_fcm_added:dev-SNAPSHOT' + playstoreImplementation 'com.github.UnifiedPush:android-connector_fcm_added:1.0.0' //Fdroid fdroidApi 'com.theartofdev.edmodo:android-image-cropper:2.8.+' @@ -201,5 +203,26 @@ dependencies { } +def getCurrentFlavor() { + Gradle gradle = getGradle() + String tskReqStr = gradle.getStartParameter().getTaskRequests().toString() -apply plugin: 'com.google.gms.google-services' + Pattern pattern + + if( tskReqStr.contains( "assemble" ) ) + pattern = Pattern.compile("assemble(\\w+)(Release|Debug)") + else + pattern = Pattern.compile("generate(\\w+)(Release|Debug)") + Matcher matcher = pattern.matcher( tskReqStr ) + + if( matcher.find() ) { + return matcher.group(1).toLowerCase() + }else + { + println "NO MATCH FOUND" + return "" + } +} +if( getCurrentFlavor() == "playstore") { + apply plugin: 'com.google.gms.google-services' +} diff --git a/app/src/fdroidcommon/java/app/fedilab/android/helper/PushHelper.java b/app/src/fdroidcommon/java/app/fedilab/android/helper/PushHelper.java index f0760c53f..24aa114a3 100644 --- a/app/src/fdroidcommon/java/app/fedilab/android/helper/PushHelper.java +++ b/app/src/fdroidcommon/java/app/fedilab/android/helper/PushHelper.java @@ -2,18 +2,27 @@ package app.fedilab.android.helper; import android.app.Activity; import android.content.Context; +import android.content.SharedPreferences; import android.database.sqlite.SQLiteDatabase; +import android.text.SpannableString; +import android.text.method.LinkMovementMethod; +import android.text.util.Linkify; +import android.widget.TextView; + +import androidx.appcompat.app.AlertDialog; import org.unifiedpush.android.connector.Registration; import java.util.List; +import app.fedilab.android.R; import app.fedilab.android.client.Entities.Account; import app.fedilab.android.jobs.ApplicationJob; import app.fedilab.android.jobs.NotificationsSyncJob; import app.fedilab.android.sqlite.AccountDAO; import app.fedilab.android.sqlite.Sqlite; +import static android.content.Context.MODE_PRIVATE; import static app.fedilab.android.helper.BaseHelper.NOTIF_NONE; import static app.fedilab.android.helper.BaseHelper.NOTIF_PUSH; import static app.fedilab.android.helper.BaseHelper.liveNotifType; @@ -31,7 +40,7 @@ public class PushHelper { List accounts = new AccountDAO(context, db).getPushNotificationAccounts(); ((Activity) context).runOnUiThread(() -> { for (Account account : accounts) { - new Registration().registerAppWithDialog(context, account.getUsername() + "@" + account.getInstance()); + registerAppWithDialog(context, account.getUsername() + "@" + account.getInstance()); } }); }).start(); @@ -41,4 +50,54 @@ public class PushHelper { break; } } + + + private static void registerAppWithDialog(Context context, String slug) { + SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, MODE_PRIVATE); + int theme = sharedpreferences.getInt(Helper.SET_THEME, Helper.THEME_DARK); + int style; + if (theme == Helper.THEME_DARK) { + style = R.style.DialogDark; + } else if (theme == Helper.THEME_BLACK) { + style = R.style.DialogBlack; + } else { + style = R.style.Dialog; + } + + Registration registration = new Registration(); + List distributors = registration.getDistributors(context); + if (distributors.size() == 1 || !registration.getDistributor(context).isEmpty()) { + if (distributors.size() == 1) { + registration.saveDistributor(context, distributors.get(0)); + } else { + registration.saveDistributor(context, registration.getDistributor(context)); + } + registration.registerApp(context, slug); + return; + } + AlertDialog.Builder alert = new AlertDialog.Builder(context, style); + if (distributors.size() == 0) { + alert.setTitle(R.string.no_distributors_found); + final TextView message = new TextView(context); + String link = "https://fedilab.app/wiki/features/push-notifications#fdroid"; + final SpannableString s = + new SpannableString(context.getString(R.string.no_distributors_explanation, link)); + Linkify.addLinks(s, Linkify.WEB_URLS); + message.setText(s); + message.setPadding(30, 20, 30, 10); + message.setMovementMethod(LinkMovementMethod.getInstance()); + alert.setView(message); + alert.setPositiveButton(R.string.close, (dialog, whichButton) -> dialog.dismiss()); + } else { + alert.setTitle(R.string.select_distributors); + String[] distributorsStr = distributors.toArray(new String[0]); + alert.setSingleChoiceItems(distributorsStr, -1, (dialog, item) -> { + String distributor = distributorsStr[item]; + registration.saveDistributor(context, distributor); + registration.registerApp(context, slug); + dialog.dismiss(); + }); + } + alert.show(); + } } diff --git a/app/src/main/java/app/fedilab/android/activities/LoginActivity.java b/app/src/main/java/app/fedilab/android/activities/LoginActivity.java index 62ac6706a..f32d68305 100644 --- a/app/src/main/java/app/fedilab/android/activities/LoginActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/LoginActivity.java @@ -15,6 +15,7 @@ package app.fedilab.android.activities; import android.Manifest; +import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; @@ -121,6 +122,7 @@ public class LoginActivity extends BaseActivity { return Helper.instanceWithProtocol(context, instance) + Helper.EP_AUTHORIZE + "?" + queryString; } + @SuppressLint("ApplySharedPref") @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -325,18 +327,23 @@ public class LoginActivity extends BaseActivity { thread.interrupt(); thread = null; } - if (oldSearch == null || !oldSearch.equals(s.toString().trim())) { thread = new Thread(() -> { try { - final String response = new HttpsConnection(LoginActivity.this, instance).get("https://instances.social/api/1.0" + action, 30, parameters, Helper.THEKINRAR_SECRET_TOKEN); + String response = null; + try { + response = new HttpsConnection(LoginActivity.this, instance).get("https://instances.social/api/1.0" + action, 30, parameters, Helper.THEKINRAR_SECRET_TOKEN); + } catch (Exception ignored) { + } + if (response == null) { return; } + String finalResponse = response; runOnUiThread(() -> { String[] instances; try { - JSONObject jsonObject = new JSONObject(response); + JSONObject jsonObject = new JSONObject(finalResponse); JSONArray jsonArray = jsonObject.getJSONArray("instances"); int length = 0; for (int i = 0; i < jsonArray.length(); i++) { @@ -744,18 +751,10 @@ public class LoginActivity extends BaseActivity { } Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.addCategory(Intent.CATEGORY_OPENABLE); - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { - intent.setType("*/*"); - String[] mimetypes = {"*/*"}; - intent.putExtra(Intent.EXTRA_MIME_TYPES, mimetypes); - startActivityForResult(intent, PICK_IMPORT); - } else { - intent.setType("*/*"); - Intent pickIntent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI); - Intent chooserIntent = Intent.createChooser(intent, getString(R.string.toot_select_import)); - chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[]{pickIntent}); - startActivityForResult(chooserIntent, PICK_IMPORT); - } + intent.setType("*/*"); + String[] mimetypes = {"*/*"}; + intent.putExtra(Intent.EXTRA_MIME_TYPES, mimetypes); + startActivityForResult(intent, PICK_IMPORT); } return super.onOptionsItemSelected(item); } diff --git a/app/src/main/java/app/fedilab/android/client/API.java b/app/src/main/java/app/fedilab/android/client/API.java index 695d40645..7205d25ee 100644 --- a/app/src/main/java/app/fedilab/android/client/API.java +++ b/app/src/main/java/app/fedilab/android/client/API.java @@ -23,6 +23,7 @@ import android.os.Bundle; import android.text.Html; import android.text.SpannableString; + import androidx.localbroadcastmanager.content.LocalBroadcastManager; import androidx.preference.PreferenceManager; @@ -5863,11 +5864,10 @@ public class API { .getDefaultSharedPreferences(context); String strPub = prefs.getString(kp_public, ""); String strPriv = prefs.getString(kp_private, ""); + ECDH ecdh = ECDH.getInstance(); if (strPub.trim().isEmpty() || strPriv.trim().isEmpty()) { newPair(context); } - - ECDH ecdh = ECDH.getInstance(); String pubKey = ecdh.getPublicKey(context); byte[] randBytes = new byte[16]; new Random().nextBytes(randBytes); diff --git a/app/src/main/java/app/fedilab/android/drawers/BaseStatusListAdapter.java b/app/src/main/java/app/fedilab/android/drawers/BaseStatusListAdapter.java index e7bbe1524..136c026be 100644 --- a/app/src/main/java/app/fedilab/android/drawers/BaseStatusListAdapter.java +++ b/app/src/main/java/app/fedilab/android/drawers/BaseStatusListAdapter.java @@ -632,15 +632,16 @@ public abstract class BaseStatusListAdapter extends RecyclerView.Adapter since_ids = new HashMap<>(); public static void task(Context context, Account account) { APIResponse apiResponse; + String key = account.getUsername() + "@" + account.getInstance(); + String last_notifid = null; + if (since_ids.containsKey(key)) { + last_notifid = since_ids.get(key); + } final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); //Check which notifications the user wants to see @@ -85,10 +91,10 @@ public class NotificationsHelper { if (!sharedpreferences.getBoolean(Helper.SET_WIFI_ONLY, false) || Helper.isOnWIFI(context)) { if (account.getSocial().compareTo("FRIENDICA") != 0 && account.getSocial().compareTo("GNU") != 0) { API api = new API(context, account.getInstance(), account.getToken()); - apiResponse = api.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, null, false); + apiResponse = api.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, last_notifid, false); } else { GNUAPI gnuApi = new GNUAPI(context, account.getInstance(), account.getToken()); - apiResponse = gnuApi.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, null); + apiResponse = gnuApi.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, last_notifid); } onRetrieveNotifications(context, apiResponse, account); } @@ -99,6 +105,8 @@ public class NotificationsHelper { List notificationsReceived = apiResponse.getNotifications(); if (apiResponse.getError() != null || notificationsReceived == null || notificationsReceived.size() == 0 || account == null) return; + String key = account.getUsername() + "@" + account.getInstance(); + since_ids.put(key, apiResponse.getNotifications().get(0).getId()); final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true); boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true); diff --git a/app/src/main/java/app/fedilab/android/jobs/BaseNotificationsSyncJob.java b/app/src/main/java/app/fedilab/android/jobs/BaseNotificationsSyncJob.java index 59a20d5dc..3ce174f64 100644 --- a/app/src/main/java/app/fedilab/android/jobs/BaseNotificationsSyncJob.java +++ b/app/src/main/java/app/fedilab/android/jobs/BaseNotificationsSyncJob.java @@ -15,55 +15,26 @@ package app.fedilab.android.jobs; * see . */ import android.content.Context; -import android.content.Intent; import android.content.SharedPreferences; import android.database.sqlite.SQLiteDatabase; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.Drawable; -import android.os.Handler; -import android.os.Looper; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.bumptech.glide.Glide; -import com.bumptech.glide.load.DataSource; -import com.bumptech.glide.load.engine.GlideException; -import com.bumptech.glide.request.RequestListener; -import com.bumptech.glide.request.target.CustomTarget; -import com.bumptech.glide.request.target.Target; -import com.bumptech.glide.request.transition.Transition; import com.evernote.android.job.Job; import com.evernote.android.job.JobManager; import com.evernote.android.job.JobRequest; -import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; -import app.fedilab.android.R; -import app.fedilab.android.activities.MainActivity; -import app.fedilab.android.client.API; -import app.fedilab.android.client.APIResponse; import app.fedilab.android.client.Entities.Account; -import app.fedilab.android.client.Entities.Notification; -import app.fedilab.android.client.GNUAPI; -import app.fedilab.android.fragments.DisplayNotificationsFragment; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.NotificationsHelper; import app.fedilab.android.sqlite.AccountDAO; import app.fedilab.android.sqlite.Sqlite; -import static app.fedilab.android.helper.Helper.INTENT_ACTION; -import static app.fedilab.android.helper.Helper.INTENT_TARGETED_ACCOUNT; -import static app.fedilab.android.helper.Helper.NOTIFICATION_INTENT; -import static app.fedilab.android.helper.Helper.PREF_INSTANCE; -import static app.fedilab.android.helper.Helper.PREF_KEY_ID; import static app.fedilab.android.helper.Helper.canNotify; -import static app.fedilab.android.helper.Helper.getMainLogo; -import static app.fedilab.android.helper.Helper.notify_user; + /** @@ -138,15 +109,7 @@ public class BaseNotificationsSyncJob extends Job { return; //Retrieve users in db that owner has. for (Account account : accounts) { - APIResponse apiResponse; - if (account.getSocial().compareTo("FRIENDICA") != 0 && account.getSocial().compareTo("GNU") != 0) { - API api = new API(getContext(), account.getInstance(), account.getToken()); - apiResponse = api.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, null, false); - } else { - GNUAPI gnuApi = new GNUAPI(getContext(), account.getInstance(), account.getToken()); - apiResponse = gnuApi.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, null); - } - NotificationsHelper.onRetrieveNotifications(getContext(), apiResponse, account); + NotificationsHelper.task(getContext(), account); } } } diff --git a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java index 1f40dd643..d85c2676b 100644 --- a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java +++ b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java @@ -15,7 +15,6 @@ package app.fedilab.android.services; * see . */ import android.content.Context; import android.database.sqlite.SQLiteDatabase; -import android.util.Log; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -23,12 +22,7 @@ import org.unifiedpush.android.connector.MessagingReceiver; import org.unifiedpush.android.connector.MessagingReceiverHandler; -import app.fedilab.android.client.API; -import app.fedilab.android.client.APIResponse; import app.fedilab.android.client.Entities.Account; -import app.fedilab.android.client.GNUAPI; -import app.fedilab.android.fragments.DisplayNotificationsFragment; -import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.NotificationsHelper; import app.fedilab.android.helper.PushNotifications; import app.fedilab.android.sqlite.AccountDAO; @@ -40,42 +34,31 @@ class handler implements MessagingReceiverHandler { @Override public void onMessage(@Nullable Context context, @NotNull String s, @NotNull String slug) { + new Thread(() -> { SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); String[] slugArray = slug.split("@"); Account account = new AccountDAO(context, db).getUniqAccountUsernameInstance(slugArray[0], slugArray[1]); - APIResponse apiResponse; - if (account.getSocial().compareTo("FRIENDICA") != 0 && account.getSocial().compareTo("GNU") != 0) { - API api = new API(context, account.getInstance(), account.getToken()); - apiResponse = api.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, null, false); - } else { - GNUAPI gnuApi = new GNUAPI(context, account.getInstance(), account.getToken()); - apiResponse = gnuApi.getNotificationsSince(DisplayNotificationsFragment.Type.ALL, null); - } - NotificationsHelper.onRetrieveNotifications(context, apiResponse, account); + NotificationsHelper.task(context, account); }).start(); } @Override public void onNewEndpoint(@Nullable Context context, @NotNull String endpoint, @NotNull String slug) { - Log.v(Helper.TAG, "onNewEndpoint: " + slug); new PushNotifications() .registerPushNotifications(context, endpoint, slug); } @Override public void onRegistrationFailed(@Nullable Context context, @NotNull String s) { - Log.v(Helper.TAG, "onRegistrationFailed"); } @Override public void onRegistrationRefused(@Nullable Context context, @NotNull String s) { - Log.v(Helper.TAG, "onRegistrationFailed"); } @Override public void onUnregistered(@Nullable Context context, @NotNull String s) { - Log.v(Helper.TAG, "onRegistrationFailed"); } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8eecf8373..21e7f8ba4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1241,5 +1241,8 @@ Video cache in MB, zero means no cache. Watermarks Automatically add a watermark at the bottom of pictures. The text can be customized for each account. + No distributors found! + You need a distributor for receiving push notifications.\nYou will find more details at %1$s.\n\nYou can also disable push notifications in settings for ignoring that message. + Select a distributor diff --git a/app/src/playstore/AndroidManifest.xml b/app/src/playstore/AndroidManifest.xml index 00e9f419d..b55a17c82 100644 --- a/app/src/playstore/AndroidManifest.xml +++ b/app/src/playstore/AndroidManifest.xml @@ -1,5 +1,5 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/app/src/playstore/java/app/fedilab/android/services/HandlerFCM.java b/app/src/playstore/java/app/fedilab/android/services/HandlerFCM.java index 92f32dad8..ed6e3a53d 100644 --- a/app/src/playstore/java/app/fedilab/android/services/HandlerFCM.java +++ b/app/src/playstore/java/app/fedilab/android/services/HandlerFCM.java @@ -11,7 +11,7 @@ public class HandlerFCM implements GetEndpointHandler { @Override public @NotNull String getEndpoint(@Nullable Context context, @NotNull String token, @NotNull String instance) { - return "https://gotify.fedilab.org/UP?token=" + token; + return "https://gotify.fedilab.app/FCM?token=" + token + "&instance=" + instance; } From c0b8c564dfb1bb08ba35380ee1774a3813c796cf Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 2 Mar 2021 16:58:13 +0100 Subject: [PATCH 32/35] Improvements --- app/build.gradle | 3 ++ .../android/activities/BaseMainActivity.java | 4 ++ .../java/app/fedilab/android/helper/ECDH.java | 48 ++++++++++++++++--- .../android/services/UnifiedPushService.java | 13 +++++ 4 files changed, 62 insertions(+), 6 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index c7a4df47f..bd04cd927 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -182,6 +182,9 @@ dependencies { implementation 'com.github.UnifiedPush:android-connector:1.1.0' implementation "com.madgag.spongycastle:bctls-jdk15on:1.58.0.0" + + + implementation 'commons-net:commons-net:3.6' //Flavors //Playstore diff --git a/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java index 9b3469253..3a1c5022a 100644 --- a/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java @@ -35,6 +35,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.Parcelable; import android.preference.PreferenceManager; +import android.util.Log; import android.util.Patterns; import android.view.LayoutInflater; import android.view.Menu; @@ -133,6 +134,7 @@ import app.fedilab.android.fragments.TabLayoutNotificationsFragment; import app.fedilab.android.fragments.TabLayoutScheduleFragment; import app.fedilab.android.fragments.WhoToFollowFragment; import app.fedilab.android.helper.CrossActions; +import app.fedilab.android.helper.ECDH; import app.fedilab.android.helper.ExpandableHeightListView; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.MenuFloating; @@ -217,6 +219,8 @@ public abstract class BaseMainActivity extends BaseActivity Account account = new AccountDAO(BaseMainActivity.this, db).getUniqAccount(userId, instance); Intent intent = getIntent(); PackageManager pm = getPackageManager(); + + ECDH.uncryptMessage(BaseMainActivity.this, "", "Underground@toot.fedilab.app"); try { if (intent != null && intent.getComponent() != null) { ActivityInfo ai = pm.getActivityInfo(intent.getComponent(), PackageManager.GET_META_DATA); diff --git a/app/src/main/java/app/fedilab/android/helper/ECDH.java b/app/src/main/java/app/fedilab/android/helper/ECDH.java index bf9fbdabf..a8246f707 100644 --- a/app/src/main/java/app/fedilab/android/helper/ECDH.java +++ b/app/src/main/java/app/fedilab/android/helper/ECDH.java @@ -17,7 +17,9 @@ package app.fedilab.android.helper; import android.annotation.SuppressLint; import android.content.Context; import android.content.SharedPreferences; +import android.database.sqlite.SQLiteDatabase; import android.util.Base64; +import android.util.Log; import androidx.preference.PreferenceManager; @@ -27,7 +29,10 @@ import org.spongycastle.asn1.x9.X9ECParameters; import org.spongycastle.crypto.params.ECNamedDomainParameters; import org.spongycastle.crypto.params.ECPrivateKeyParameters; import org.spongycastle.crypto.params.ECPublicKeyParameters; +import org.spongycastle.jce.provider.BouncyCastleProvider; import org.spongycastle.jce.spec.ECNamedCurveSpec; +import org.spongycastle.jce.spec.ECParameterSpec; +import org.spongycastle.jce.spec.ECPublicKeySpec; import org.spongycastle.math.ec.ECCurve; import org.spongycastle.math.ec.ECPoint; @@ -44,9 +49,13 @@ import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; import java.security.spec.X509EncodedKeySpec; +import javax.crypto.Cipher; import javax.crypto.KeyAgreement; +import javax.crypto.NoSuchPaddingException; import app.fedilab.android.client.Entities.Account; +import app.fedilab.android.sqlite.AccountDAO; +import app.fedilab.android.sqlite.Sqlite; // https://github.com/nelenkov/ecdh-kx/blob/master/src/org/nick/ecdhkx/Crypto.java @@ -112,8 +121,8 @@ public class ECDH { } private static byte[] generateSecret(PrivateKey myPrivKey, PublicKey otherPubKey) throws Exception { - ECPublicKey ecPubKey = (ECPublicKey) otherPubKey; - KeyAgreement keyAgreement = KeyAgreement.getInstance(KEGEN_ALG, PROVIDER); + + KeyAgreement keyAgreement = KeyAgreement.getInstance(KEGEN_ALG); keyAgreement.init(myPrivKey); keyAgreement.doPhase(otherPubKey, true); @@ -188,10 +197,37 @@ public class ECDH { } - static synchronized PublicKey readPublicKey(String keyStr) throws Exception { - X509EncodedKeySpec x509ks = new X509EncodedKeySpec( - base64Decode(keyStr)); - return kf.generatePublic(x509ks); + public static String uncryptMessage(Context context, String cyphered, String slug) { + SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + String crypted_message_test = sharedpreferences.getString("CRYPTED_MESSAGE_TEST", null); + Log.v(Helper.TAG, "crypted_message_test: " + crypted_message_test); + getInstance(); + SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + String[] slugArray = slug.split("@"); + Account account = new AccountDAO(context, db).getUniqAccountUsernameInstance(slugArray[0], slugArray[1]); + + String uncrypted = crypted_message_test; + + try { + PublicKey dddd = getServerKey(context, account); + } catch (Exception e) { + e.printStackTrace(); + } + byte[] ddd = getSharedSecret(context, account); + + return ""; + + + } + + + public static PublicKey readPublicKey(String keyStr) throws Exception { + KeyFactory kf = KeyFactory.getInstance("ECDH", new BouncyCastleProvider()); + ECParameterSpec parameterSpec = org.spongycastle.jce.ECNamedCurveTable.getParameterSpec("prime256v1"); + ECCurve curve = parameterSpec.getCurve(); + ECPoint point = curve.decodePoint(base64Decode(keyStr)); + ECPublicKeySpec pubSpec = new ECPublicKeySpec(point, parameterSpec); + return kf.generatePublic(pubSpec); } diff --git a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java index d85c2676b..39c998158 100644 --- a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java +++ b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java @@ -14,7 +14,9 @@ package app.fedilab.android.services; * You should have received a copy of the GNU General Public License along with Fedilab; if not, * see . */ import android.content.Context; +import android.content.SharedPreferences; import android.database.sqlite.SQLiteDatabase; +import android.util.Log; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -23,6 +25,7 @@ import org.unifiedpush.android.connector.MessagingReceiverHandler; import app.fedilab.android.client.Entities.Account; +import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.NotificationsHelper; import app.fedilab.android.helper.PushNotifications; import app.fedilab.android.sqlite.AccountDAO; @@ -35,6 +38,16 @@ class handler implements MessagingReceiverHandler { @Override public void onMessage(@Nullable Context context, @NotNull String s, @NotNull String slug) { + //TODO: remove after tests + SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + String crypted_message_test = sharedpreferences.getString("CRYPTED_MESSAGE_TEST", null); + Log.v(Helper.TAG, "crypted_message_test: " + crypted_message_test); + if (crypted_message_test == null) { + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString("CRYPTED_MESSAGE_TEST", s); + editor.apply(); + } + new Thread(() -> { SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); String[] slugArray = slug.split("@"); From 1e9bbf4d5c94027f624ac39825fc717f71cdfe38 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 2 Mar 2021 19:16:01 +0100 Subject: [PATCH 33/35] Improvements --- .../android/activities/BaseMainActivity.java | 1 - .../java/app/fedilab/android/helper/ECDH.java | 41 ++++++++++++++----- .../android/services/UnifiedPushService.java | 3 ++ 3 files changed, 34 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java index 3a1c5022a..563c42274 100644 --- a/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java @@ -220,7 +220,6 @@ public abstract class BaseMainActivity extends BaseActivity Intent intent = getIntent(); PackageManager pm = getPackageManager(); - ECDH.uncryptMessage(BaseMainActivity.this, "", "Underground@toot.fedilab.app"); try { if (intent != null && intent.getComponent() != null) { ActivityInfo ai = pm.getActivityInfo(intent.getComponent(), PackageManager.GET_META_DATA); diff --git a/app/src/main/java/app/fedilab/android/helper/ECDH.java b/app/src/main/java/app/fedilab/android/helper/ECDH.java index a8246f707..30addc8c6 100644 --- a/app/src/main/java/app/fedilab/android/helper/ECDH.java +++ b/app/src/main/java/app/fedilab/android/helper/ECDH.java @@ -23,6 +23,7 @@ import android.util.Log; import androidx.preference.PreferenceManager; + import org.spongycastle.asn1.ASN1ObjectIdentifier; import org.spongycastle.asn1.x9.ECNamedCurveTable; import org.spongycastle.asn1.x9.X9ECParameters; @@ -36,7 +37,9 @@ import org.spongycastle.jce.spec.ECPublicKeySpec; import org.spongycastle.math.ec.ECCurve; import org.spongycastle.math.ec.ECPoint; +import java.io.ByteArrayInputStream; import java.math.BigInteger; +import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -47,11 +50,13 @@ import java.security.PublicKey; import java.security.Security; import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; -import java.security.spec.X509EncodedKeySpec; +import java.util.ArrayList; import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; import javax.crypto.KeyAgreement; -import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.SecretKeySpec; + import app.fedilab.android.client.Entities.Account; import app.fedilab.android.sqlite.AccountDAO; @@ -198,26 +203,37 @@ public class ECDH { public static String uncryptMessage(Context context, String cyphered, String slug) { - SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - String crypted_message_test = sharedpreferences.getString("CRYPTED_MESSAGE_TEST", null); - Log.v(Helper.TAG, "crypted_message_test: " + crypted_message_test); getInstance(); SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); String[] slugArray = slug.split("@"); Account account = new AccountDAO(context, db).getUniqAccountUsernameInstance(slugArray[0], slugArray[1]); - String uncrypted = crypted_message_test; + byte[] privateKey = getSharedSecret(context, account); try { - PublicKey dddd = getServerKey(context, account); + Cipher outCipher = Cipher.getInstance("ECIES", "SC"); + outCipher.init(Cipher.DECRYPT_MODE, readPrivateKey(privateKey)); + + CipherInputStream cipherInputStream = new CipherInputStream( + new ByteArrayInputStream(Base64.decode(cyphered, Base64.DEFAULT)), outCipher); + + ArrayList values = new ArrayList<>(); + int nextByte; + while ((nextByte = cipherInputStream.read()) != -1) { + values.add((byte) nextByte); + } + byte[] bytes = new byte[values.size()]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = values.get(i).byteValue(); + } + String finalText = new String(bytes, 0, bytes.length, StandardCharsets.UTF_8); + Log.v(Helper.TAG, "---> " + finalText); + return finalText; } catch (Exception e) { e.printStackTrace(); } - byte[] ddd = getSharedSecret(context, account); - return ""; - } @@ -231,6 +247,11 @@ public class ECDH { } + public static PrivateKey readPrivateKey(byte[] key) throws Exception { + SecretKeySpec signingKey = new SecretKeySpec(key, "ECIES"); + return kf.generatePrivate(signingKey); + } + static synchronized PrivateKey readMyPrivateKey(Context context) throws Exception { X9ECParameters x9 = ECNamedCurveTable.getByName("prime256v1"); ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID("prime256v1"); diff --git a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java index 39c998158..ab93943bc 100644 --- a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java +++ b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java @@ -24,7 +24,9 @@ import org.unifiedpush.android.connector.MessagingReceiver; import org.unifiedpush.android.connector.MessagingReceiverHandler; +import app.fedilab.android.activities.BaseMainActivity; import app.fedilab.android.client.Entities.Account; +import app.fedilab.android.helper.ECDH; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.NotificationsHelper; import app.fedilab.android.helper.PushNotifications; @@ -51,6 +53,7 @@ class handler implements MessagingReceiverHandler { new Thread(() -> { SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); String[] slugArray = slug.split("@"); + //ECDH.uncryptMessage(context, s, slug); Account account = new AccountDAO(context, db).getUniqAccountUsernameInstance(slugArray[0], slugArray[1]); NotificationsHelper.task(context, account); }).start(); From 16ae6422432ac48fef2d29a83dcf11f8c5f09c80 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 3 Mar 2021 11:32:43 +0100 Subject: [PATCH 34/35] Improvements --- .../android/activities/BaseMainActivity.java | 1 - .../java/app/fedilab/android/helper/ECDH.java | 33 +++++++------------ .../android/services/UnifiedPushService.java | 33 +++++++------------ .../fedilab/android/helper/PushHelper.java | 1 - 4 files changed, 23 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java index 563c42274..f34ab9c91 100644 --- a/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/BaseMainActivity.java @@ -35,7 +35,6 @@ import android.os.Bundle; import android.os.Handler; import android.os.Parcelable; import android.preference.PreferenceManager; -import android.util.Log; import android.util.Patterns; import android.view.LayoutInflater; import android.view.Menu; diff --git a/app/src/main/java/app/fedilab/android/helper/ECDH.java b/app/src/main/java/app/fedilab/android/helper/ECDH.java index 30addc8c6..46a6247ff 100644 --- a/app/src/main/java/app/fedilab/android/helper/ECDH.java +++ b/app/src/main/java/app/fedilab/android/helper/ECDH.java @@ -19,7 +19,6 @@ import android.content.Context; import android.content.SharedPreferences; import android.database.sqlite.SQLiteDatabase; import android.util.Base64; -import android.util.Log; import androidx.preference.PreferenceManager; @@ -33,7 +32,9 @@ import org.spongycastle.crypto.params.ECPublicKeyParameters; import org.spongycastle.jce.provider.BouncyCastleProvider; import org.spongycastle.jce.spec.ECNamedCurveSpec; import org.spongycastle.jce.spec.ECParameterSpec; +import org.spongycastle.jce.spec.ECPrivateKeySpec; import org.spongycastle.jce.spec.ECPublicKeySpec; +import org.spongycastle.jce.spec.IEKeySpec; import org.spongycastle.math.ec.ECCurve; import org.spongycastle.math.ec.ECPoint; @@ -50,6 +51,7 @@ import java.security.PublicKey; import java.security.Security; import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; +import java.security.spec.PKCS8EncodedKeySpec; import java.util.ArrayList; import javax.crypto.Cipher; @@ -114,7 +116,7 @@ public class ECDH { } static byte[] base64Decode(String str) { - return Base64.decode(str, Base64.URL_SAFE | Base64.NO_PADDING | Base64.NO_WRAP); + return Base64.decode(str, Base64.DEFAULT); } static synchronized KeyPair generateKeyPair() @@ -207,27 +209,14 @@ public class ECDH { SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); String[] slugArray = slug.split("@"); Account account = new AccountDAO(context, db).getUniqAccountUsernameInstance(slugArray[0], slugArray[1]); - byte[] privateKey = getSharedSecret(context, account); - try { Cipher outCipher = Cipher.getInstance("ECIES", "SC"); + // outCipher.init(Cipher.DECRYPT_MODE, readPrivateKey(privateKey)); outCipher.init(Cipher.DECRYPT_MODE, readPrivateKey(privateKey)); - CipherInputStream cipherInputStream = new CipherInputStream( - new ByteArrayInputStream(Base64.decode(cyphered, Base64.DEFAULT)), outCipher); - - ArrayList values = new ArrayList<>(); - int nextByte; - while ((nextByte = cipherInputStream.read()) != -1) { - values.add((byte) nextByte); - } - byte[] bytes = new byte[values.size()]; - for (int i = 0; i < bytes.length; i++) { - bytes[i] = values.get(i).byteValue(); - } - String finalText = new String(bytes, 0, bytes.length, StandardCharsets.UTF_8); - Log.v(Helper.TAG, "---> " + finalText); + byte[] plaintext = outCipher.doFinal(cyphered.getBytes(StandardCharsets.UTF_8)); + String finalText = new String(plaintext); return finalText; } catch (Exception e) { e.printStackTrace(); @@ -248,8 +237,10 @@ public class ECDH { public static PrivateKey readPrivateKey(byte[] key) throws Exception { - SecretKeySpec signingKey = new SecretKeySpec(key, "ECIES"); - return kf.generatePrivate(signingKey); + KeyFactory kf = KeyFactory.getInstance("ECDH", new BouncyCastleProvider()); + ECParameterSpec parameterSpec = org.spongycastle.jce.ECNamedCurveTable.getParameterSpec("prime256v1"); + ECPrivateKeySpec pubSpec = new ECPrivateKeySpec(new BigInteger(1, key), parameterSpec); + return kf.generatePrivate(pubSpec); } static synchronized PrivateKey readMyPrivateKey(Context context) throws Exception { @@ -271,7 +262,7 @@ public class ECDH { .getDefaultSharedPreferences(context); String strPub = prefs.getString(kp_public, ""); String strPriv = prefs.getString(kp_private, ""); - if (strPub.trim().isEmpty() || strPriv.trim().isEmpty()) { + if (strPub.trim().isEmpty() || strPriv.trim().isEmpty() || 1 == 1) { return newPair(context); } try { diff --git a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java index ab93943bc..bd244299d 100644 --- a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java +++ b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java @@ -14,9 +14,7 @@ package app.fedilab.android.services; * You should have received a copy of the GNU General Public License along with Fedilab; if not, * see . */ import android.content.Context; -import android.content.SharedPreferences; import android.database.sqlite.SQLiteDatabase; -import android.util.Log; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -24,10 +22,7 @@ import org.unifiedpush.android.connector.MessagingReceiver; import org.unifiedpush.android.connector.MessagingReceiverHandler; -import app.fedilab.android.activities.BaseMainActivity; import app.fedilab.android.client.Entities.Account; -import app.fedilab.android.helper.ECDH; -import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.NotificationsHelper; import app.fedilab.android.helper.PushNotifications; import app.fedilab.android.sqlite.AccountDAO; @@ -40,29 +35,23 @@ class handler implements MessagingReceiverHandler { @Override public void onMessage(@Nullable Context context, @NotNull String s, @NotNull String slug) { - //TODO: remove after tests - SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); - String crypted_message_test = sharedpreferences.getString("CRYPTED_MESSAGE_TEST", null); - Log.v(Helper.TAG, "crypted_message_test: " + crypted_message_test); - if (crypted_message_test == null) { - SharedPreferences.Editor editor = sharedpreferences.edit(); - editor.putString("CRYPTED_MESSAGE_TEST", s); - editor.apply(); - } - new Thread(() -> { - SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); - String[] slugArray = slug.split("@"); - //ECDH.uncryptMessage(context, s, slug); - Account account = new AccountDAO(context, db).getUniqAccountUsernameInstance(slugArray[0], slugArray[1]); - NotificationsHelper.task(context, account); + if (context != null) { + SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); + String[] slugArray = slug.split("@"); + //ECDH.uncryptMessage(context, s, slug); + Account account = new AccountDAO(context, db).getUniqAccountUsernameInstance(slugArray[0], slugArray[1]); + NotificationsHelper.task(context, account); + } }).start(); } @Override public void onNewEndpoint(@Nullable Context context, @NotNull String endpoint, @NotNull String slug) { - new PushNotifications() - .registerPushNotifications(context, endpoint, slug); + if (context != null) { + new PushNotifications() + .registerPushNotifications(context, endpoint, slug); + } } @Override diff --git a/app/src/playstore/java/app/fedilab/android/helper/PushHelper.java b/app/src/playstore/java/app/fedilab/android/helper/PushHelper.java index db1a814e4..04735db05 100644 --- a/app/src/playstore/java/app/fedilab/android/helper/PushHelper.java +++ b/app/src/playstore/java/app/fedilab/android/helper/PushHelper.java @@ -6,7 +6,6 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.sqlite.SQLiteDatabase; -import android.util.Log; import org.unifiedpush.android.connector_fcm_added.RegistrationFCM; From 5930f71fe60000b89fdaf51b1410a89290747fce Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 3 Mar 2021 14:30:45 +0100 Subject: [PATCH 35/35] Improvements --- .../java/app/fedilab/android/client/API.java | 3 +- .../java/app/fedilab/android/helper/ECDH.java | 84 ++++++++----------- .../android/services/UnifiedPushService.java | 4 +- 3 files changed, 41 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/client/API.java b/app/src/main/java/app/fedilab/android/client/API.java index 7205d25ee..252dc9ee7 100644 --- a/app/src/main/java/app/fedilab/android/client/API.java +++ b/app/src/main/java/app/fedilab/android/client/API.java @@ -112,7 +112,6 @@ import app.fedilab.android.sqlite.TimelineCacheDAO; import static app.fedilab.android.helper.ECDH.kp_private; import static app.fedilab.android.helper.ECDH.kp_public; -import static app.fedilab.android.helper.ECDH.newPair; /** @@ -5866,7 +5865,7 @@ public class API { String strPriv = prefs.getString(kp_private, ""); ECDH ecdh = ECDH.getInstance(); if (strPub.trim().isEmpty() || strPriv.trim().isEmpty()) { - newPair(context); + ecdh.newPair(context); } String pubKey = ecdh.getPublicKey(context); byte[] randBytes = new byte[16]; diff --git a/app/src/main/java/app/fedilab/android/helper/ECDH.java b/app/src/main/java/app/fedilab/android/helper/ECDH.java index 46a6247ff..82cc80874 100644 --- a/app/src/main/java/app/fedilab/android/helper/ECDH.java +++ b/app/src/main/java/app/fedilab/android/helper/ECDH.java @@ -19,28 +19,21 @@ import android.content.Context; import android.content.SharedPreferences; import android.database.sqlite.SQLiteDatabase; import android.util.Base64; - import androidx.preference.PreferenceManager; - - import org.spongycastle.asn1.ASN1ObjectIdentifier; import org.spongycastle.asn1.x9.ECNamedCurveTable; import org.spongycastle.asn1.x9.X9ECParameters; import org.spongycastle.crypto.params.ECNamedDomainParameters; import org.spongycastle.crypto.params.ECPrivateKeyParameters; import org.spongycastle.crypto.params.ECPublicKeyParameters; -import org.spongycastle.jce.provider.BouncyCastleProvider; import org.spongycastle.jce.spec.ECNamedCurveSpec; import org.spongycastle.jce.spec.ECParameterSpec; import org.spongycastle.jce.spec.ECPrivateKeySpec; import org.spongycastle.jce.spec.ECPublicKeySpec; -import org.spongycastle.jce.spec.IEKeySpec; import org.spongycastle.math.ec.ECCurve; import org.spongycastle.math.ec.ECPoint; -import java.io.ByteArrayInputStream; import java.math.BigInteger; -import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -51,20 +44,16 @@ import java.security.PublicKey; import java.security.Security; import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; -import java.security.spec.PKCS8EncodedKeySpec; -import java.util.ArrayList; import javax.crypto.Cipher; -import javax.crypto.CipherInputStream; import javax.crypto.KeyAgreement; -import javax.crypto.spec.SecretKeySpec; import app.fedilab.android.client.Entities.Account; import app.fedilab.android.sqlite.AccountDAO; import app.fedilab.android.sqlite.Sqlite; -// https://github.com/nelenkov/ecdh-kx/blob/master/src/org/nick/ecdhkx/Crypto.java + public class ECDH { @@ -76,24 +65,26 @@ public class ECDH { public static final String kp_private = "kp_private"; public static final String KEGEN_ALG = "ECDH"; + public static final String name = "prime256v1"; + private static final String kp_public_affine_x = "kp_public_affine_x"; private static final String kp_public_affine_y = "kp_public_affine_y"; private static ECDH instance; - static { Security.addProvider(new org.spongycastle.jce.provider.BouncyCastleProvider()); } - public static KeyFactory kf = null; - private static KeyPairGenerator kpg = null; + public final KeyFactory kf; + private final KeyPairGenerator kpg; public ECDH() { try { kf = KeyFactory.getInstance(KEGEN_ALG, PROVIDER); kpg = KeyPairGenerator.getInstance(KEGEN_ALG, PROVIDER); + } catch (NoSuchAlgorithmException | NoSuchProviderException e) { e.printStackTrace(); throw new RuntimeException(e); @@ -104,7 +95,6 @@ public class ECDH { if (instance == null) { instance = new ECDH(); } - return instance; } @@ -119,16 +109,15 @@ public class ECDH { return Base64.decode(str, Base64.DEFAULT); } - static synchronized KeyPair generateKeyPair() + synchronized KeyPair generateKeyPair() throws Exception { - ECGenParameterSpec ecParamSpec = new ECGenParameterSpec("prime256v1"); + ECGenParameterSpec ecParamSpec = new ECGenParameterSpec(name); kpg.initialize(ecParamSpec); return kpg.generateKeyPair(); } - private static byte[] generateSecret(PrivateKey myPrivKey, PublicKey otherPubKey) throws Exception { - + private byte[] generateSecret(PrivateKey myPrivKey, PublicKey otherPubKey) throws Exception { KeyAgreement keyAgreement = KeyAgreement.getInstance(KEGEN_ALG); keyAgreement.init(myPrivKey); keyAgreement.doPhase(otherPubKey, true); @@ -137,13 +126,13 @@ public class ECDH { } - static synchronized KeyPair readKeyPair(Context context) + synchronized KeyPair readKeyPair(Context context) throws Exception { return new KeyPair(readMyPublicKey(context), readMyPrivateKey(context)); } @SuppressLint("ApplySharedPref") - public static KeyPair newPair(Context context) { + public KeyPair newPair(Context context) { SharedPreferences.Editor prefsEditor = PreferenceManager .getDefaultSharedPreferences(context).edit(); KeyPair kp; @@ -159,8 +148,8 @@ public class ECDH { byte[] y = key.getW().getAffineY().toByteArray(); BigInteger xbi = new BigInteger(1, x); BigInteger ybi = new BigInteger(1, y); - X9ECParameters x9 = ECNamedCurveTable.getByName("prime256v1"); - ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID("prime256v1"); + X9ECParameters x9 = ECNamedCurveTable.getByName(name); + ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID(name); ECCurve curve = x9.getCurve(); ECPoint point = curve.createPoint(xbi, ybi); @@ -184,10 +173,10 @@ public class ECDH { } - static synchronized PublicKey readMyPublicKey(Context context) throws Exception { + synchronized PublicKey readMyPublicKey(Context context) throws Exception { - X9ECParameters x9 = ECNamedCurveTable.getByName("prime256v1"); - ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID("prime256v1"); + X9ECParameters x9 = ECNamedCurveTable.getByName(name); + ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID(name); SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(context); @@ -198,24 +187,23 @@ public class ECDH { x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed()); - ECNamedCurveSpec ecNamedCurveSpec = new ECNamedCurveSpec("prime256v1", dParams.getCurve(), dParams.getG(), dParams.getN()); + ECNamedCurveSpec ecNamedCurveSpec = new ECNamedCurveSpec(name, dParams.getCurve(), dParams.getG(), dParams.getN()); java.security.spec.ECPoint w = new java.security.spec.ECPoint(xbi, ybi); return kf.generatePublic(new java.security.spec.ECPublicKeySpec(w, ecNamedCurveSpec)); } - public static String uncryptMessage(Context context, String cyphered, String slug) { + public String uncryptMessage(Context context, String cyphered, String slug) { getInstance(); SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); String[] slugArray = slug.split("@"); Account account = new AccountDAO(context, db).getUniqAccountUsernameInstance(slugArray[0], slugArray[1]); byte[] privateKey = getSharedSecret(context, account); try { - Cipher outCipher = Cipher.getInstance("ECIES", "SC"); + Cipher outCipher = Cipher.getInstance("ECIES", PROVIDER); // outCipher.init(Cipher.DECRYPT_MODE, readPrivateKey(privateKey)); outCipher.init(Cipher.DECRYPT_MODE, readPrivateKey(privateKey)); - - byte[] plaintext = outCipher.doFinal(cyphered.getBytes(StandardCharsets.UTF_8)); + byte[] plaintext = outCipher.doFinal(Base64.decode(cyphered, Base64.DEFAULT)); String finalText = new String(plaintext); return finalText; } catch (Exception e) { @@ -226,9 +214,8 @@ public class ECDH { } - public static PublicKey readPublicKey(String keyStr) throws Exception { - KeyFactory kf = KeyFactory.getInstance("ECDH", new BouncyCastleProvider()); - ECParameterSpec parameterSpec = org.spongycastle.jce.ECNamedCurveTable.getParameterSpec("prime256v1"); + public PublicKey readPublicKey(String keyStr) throws Exception { + ECParameterSpec parameterSpec = org.spongycastle.jce.ECNamedCurveTable.getParameterSpec(name); ECCurve curve = parameterSpec.getCurve(); ECPoint point = curve.decodePoint(base64Decode(keyStr)); ECPublicKeySpec pubSpec = new ECPublicKeySpec(point, parameterSpec); @@ -236,33 +223,32 @@ public class ECDH { } - public static PrivateKey readPrivateKey(byte[] key) throws Exception { - KeyFactory kf = KeyFactory.getInstance("ECDH", new BouncyCastleProvider()); - ECParameterSpec parameterSpec = org.spongycastle.jce.ECNamedCurveTable.getParameterSpec("prime256v1"); + public PrivateKey readPrivateKey(byte[] key) throws Exception { + ECParameterSpec parameterSpec = org.spongycastle.jce.ECNamedCurveTable.getParameterSpec(name); ECPrivateKeySpec pubSpec = new ECPrivateKeySpec(new BigInteger(1, key), parameterSpec); return kf.generatePrivate(pubSpec); } - static synchronized PrivateKey readMyPrivateKey(Context context) throws Exception { - X9ECParameters x9 = ECNamedCurveTable.getByName("prime256v1"); - ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID("prime256v1"); + synchronized PrivateKey readMyPrivateKey(Context context) throws Exception { + X9ECParameters x9 = ECNamedCurveTable.getByName(name); + ASN1ObjectIdentifier oid = ECNamedCurveTable.getOID(name); SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(context); BigInteger ybi = new BigInteger(prefs.getString(kp_public_affine_y, "0")); ECNamedDomainParameters dParams = new ECNamedDomainParameters(oid, x9.getCurve(), x9.getG(), x9.getN(), x9.getH(), x9.getSeed()); - ECNamedCurveSpec ecNamedCurveSpec = new ECNamedCurveSpec("prime256v1", dParams.getCurve(), dParams.getG(), dParams.getN()); + ECNamedCurveSpec ecNamedCurveSpec = new ECNamedCurveSpec(name, dParams.getCurve(), dParams.getG(), dParams.getN()); return kf.generatePrivate(new java.security.spec.ECPrivateKeySpec(ybi, ecNamedCurveSpec)); } - private static synchronized KeyPair getPair(Context context) { + private synchronized KeyPair getPair(Context context) { SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(context); String strPub = prefs.getString(kp_public, ""); String strPriv = prefs.getString(kp_private, ""); - if (strPub.trim().isEmpty() || strPriv.trim().isEmpty() || 1 == 1) { + if (strPub.trim().isEmpty() || strPriv.trim().isEmpty()) { return newPair(context); } try { @@ -273,7 +259,7 @@ public class ECDH { return null; } - static PublicKey getServerKey(Context context, Account account) throws Exception { + PublicKey getServerKey(Context context, Account account) throws Exception { SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(context); String serverKey = prefs.getString(peer_public + account.getId() + account.getInstance(), ""); @@ -281,13 +267,17 @@ public class ECDH { } @SuppressWarnings({"unused", "RedundantSuppression"}) - public static byte[] getSharedSecret(Context context, Account account) { + public byte[] getSharedSecret(Context context, Account account) { try { - return generateSecret(getPair(context).getPrivate(), getServerKey(context, account)); + KeyPair keyPair = getPair(context); + if (keyPair != null) { + return generateSecret(keyPair.getPrivate(), getServerKey(context, account)); + } } catch (Exception e) { e.printStackTrace(); return null; } + return null; } public String getPublicKey(Context context) { diff --git a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java index bd244299d..e9ffec29b 100644 --- a/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java +++ b/app/src/main/java/app/fedilab/android/services/UnifiedPushService.java @@ -23,6 +23,7 @@ import org.unifiedpush.android.connector.MessagingReceiverHandler; import app.fedilab.android.client.Entities.Account; +import app.fedilab.android.helper.ECDH; import app.fedilab.android.helper.NotificationsHelper; import app.fedilab.android.helper.PushNotifications; import app.fedilab.android.sqlite.AccountDAO; @@ -39,7 +40,8 @@ class handler implements MessagingReceiverHandler { if (context != null) { SQLiteDatabase db = Sqlite.getInstance(context.getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open(); String[] slugArray = slug.split("@"); - //ECDH.uncryptMessage(context, s, slug); + ECDH ecdh = ECDH.getInstance(); + //ecdh.uncryptMessage(context, s, slug); Account account = new AccountDAO(context, db).getUniqAccountUsernameInstance(slugArray[0], slugArray[1]); NotificationsHelper.task(context, account); }