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' }
}
}