unifiedpush - first changes

This commit is contained in:
Thomas 2021-02-21 17:18:37 +01:00
parent c73fb06bd7
commit f7702ecc12
17 changed files with 618 additions and 6 deletions

View File

@ -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

View File

@ -84,5 +84,20 @@
<activity
android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
android:theme="@style/Base.Theme.AppCompat" />
<receiver
android:name=".services.UnifiedPushService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="org.unifiedpush.android.connector.MESSAGE" />
<action android:name="org.unifiedpush.android.connector.UNREGISTERED" />
<action android:name="org.unifiedpush.android.connector.NEW_ENDPOINT" />
<action android:name="org.unifiedpush.android.connector.REGISTRATION_FAILED" />
<action android:name="org.unifiedpush.android.connector.REGISTRATION_REFUSED" />
</intent-filter>
</receiver>
</application>
</manifest>

View File

@ -460,6 +460,18 @@
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
<receiver
android:name=".services.UnifiedPushService"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="org.unifiedpush.android.connector.MESSAGE" />
<action android:name="org.unifiedpush.android.connector.UNREGISTERED" />
<action android:name="org.unifiedpush.android.connector.NEW_ENDPOINT" />
<action android:name="org.unifiedpush.android.connector.REGISTRATION_FAILED" />
<action android:name="org.unifiedpush.android.connector.REGISTRATION_REFUSED" />
</intent-filter>
</receiver>
</application>
</manifest>

View File

@ -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,

View File

@ -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 <http://www.gnu.org/licenses>. */
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<Context> 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();
}
}

View File

@ -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<String> iter = alertsObject.keys();
HashMap<String, Boolean> 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<String, String> 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
*

View File

@ -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<Notification> notifications = null;
private List<Relationship> relationships = null;
private List<Announcement> announcements = null;
private PushSubscription pushSubscription;
private String targetedId = null;
private Results results = null;
private List<HowToVideo> 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<Account> accounts) {
this.accounts = accounts;
}

View File

@ -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 <http://www.gnu.org/licenses>. */
import java.util.HashMap;
public class PushSubscription {
private String id;
private String endpoint;
private HashMap<String, Boolean> 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<String, Boolean> getAlertsList() {
return alerts;
}
public void setAlertsList(HashMap<String, Boolean> alertsList) {
this.alerts = alertsList;
}
public String getServer_key() {
return server_key;
}
public void setServer_key(String server_key) {
this.server_key = server_key;
}
}

View File

@ -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);

View File

@ -640,10 +640,27 @@ public abstract class BaseStatusListAdapter extends RecyclerView.Adapter<Recycle
for (int j = 0; j <= ident; j++) {
View view = new View(context);
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(1, LinearLayout.LayoutParams.MATCH_PARENT);
params.setMargins((int) Helper.convertDpToPixel(5, context), 0, 0, 0);
params.setMargins((int) Helper.convertDpToPixel(1, context), 0, 0, 0);
view.setLayoutParams(params);
holder.decoration_container.addView(view, 0);
view.setBackgroundResource(R.color.cyanea_accent_reference);
int colorPosition = (ident - j) % 7 + 1;
int color = R.color.cyanea_accent_reference;
if (colorPosition == 1) {
color = R.color.rainbow_1;
} else if (colorPosition == 2) {
color = R.color.rainbow_2;
} else if (colorPosition == 3) {
color = R.color.rainbow_3;
} else if (colorPosition == 4) {
color = R.color.rainbow_4;
} else if (colorPosition == 5) {
color = R.color.rainbow_5;
} else if (colorPosition == 6) {
color = R.color.rainbow_6;
} else if (colorPosition == 7) {
color = R.color.rainbow_7;
}
view.setBackgroundResource(color);
}
}

View File

@ -254,8 +254,8 @@ public class BaseHelper {
public static final String TAG = "mastodon_etalab";
public static final String CLIENT_NAME_VALUE = "Fedilab";
public static final String OAUTH_SCOPES = "read write follow";
public static final String OAUTH_SCOPES_ADMIN = "read write follow admin:read admin:write admin";
public static final String OAUTH_SCOPES = "read write follow push";
public static final String OAUTH_SCOPES_ADMIN = "read write follow push admin:read admin:write admin";
public static final String OAUTH_SCOPES_PEERTUBE = "user";
public static final String PREF_KEY_OAUTH_TOKEN = "oauth_token";

View File

@ -0,0 +1,182 @@
package app.fedilab.android.helper;
import android.content.Context;
import android.content.SharedPreferences;
import android.util.Base64;
import android.util.Log;
import androidx.preference.PreferenceManager;
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.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import javax.crypto.KeyAgreement;
// https://github.com/nelenkov/ecdh-kx/blob/master/src/org/nick/ecdhkx/Crypto.java
public class ECDH {
private static final String kp_public = "kp_public";
private static final String kp_private = "kp_private";
private static final String peer_public = "peer_public";
private static final String TAG = ECDH.class.getSimpleName();
private static final String PROVIDER = "SC";
private static final String KEGEN_ALG = "ECDH";
private static ECDH instance;
static {
Security.insertProviderAt(new org.spongycastle.jce.provider.BouncyCastleProvider(), 1);
}
private 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) {
throw new RuntimeException(e);
}
}
public static synchronized ECDH getInstance() {
if (instance == null) {
instance = new ECDH();
}
return instance;
}
static String base64Encode(byte[] b) {
return Base64.encodeToString(b, Base64.DEFAULT);
}
static byte[] base64Decode(String str) {
return Base64.decode(str, Base64.DEFAULT);
}
synchronized KeyPair generateKeyPair()
throws Exception {
ECGenParameterSpec ecParamSpec = new ECGenParameterSpec("secp256k1");
kpg.initialize(ecParamSpec);
return kpg.generateKeyPair();
}
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);
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)
throws Exception {
return new KeyPair(readPublicKey(pubKeyStr), readPrivateKey(privKeyStr));
}
KeyPair newPair(Context context) {
SharedPreferences.Editor prefsEditor = PreferenceManager
.getDefaultSharedPreferences(context).edit();
KeyPair kp = null;
try {
kp = generateKeyPair();
} catch (Exception e) {
e.printStackTrace();
return null;
}
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) {
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context);
String strPub = prefs.getString(kp_public, "");
String strPriv = prefs.getString(kp_private, "");
if (strPub.isEmpty() || strPriv.isEmpty() || 1 == 1) {
return newPair(context);
}
try {
return readKeyPair(strPub, strPriv);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
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());
}
void saveServerKey(Context context, String strPeerPublic) {
SharedPreferences.Editor prefsEditor = PreferenceManager
.getDefaultSharedPreferences(context).edit();
prefsEditor.putString(peer_public, strPeerPublic);
prefsEditor.commit();
}
PublicKey getServerKey(Context context) throws Exception {
return readPublicKey(
PreferenceManager.getDefaultSharedPreferences(context)
.getString(peer_public, "")
);
}
byte[] getSecret(Context context) {
try {
return generateSecret(getPair(context).getPrivate(), getServerKey(context));
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}

View File

@ -0,0 +1,32 @@
package app.fedilab.android.helper;
import android.content.Context;
import android.util.Base64;
import java.util.Random;
public class PushNotifications {
public void registerPushNotifications(Context context, String endpoint) {
ECDH ecdh = new ECDH();
String pubKey = ecdh.getPublicKey(context);
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);
}
public void displayNotification(Context context, String ciphered) {
ECDH ecdh = new ECDH();
byte[] secret = ecdh.getSecret(context);
//process with the event
// https://openacs.org/webpush-demo/report.html
// decrypt using AES 128 GCM
}
}

View File

@ -0,0 +1,21 @@
/* 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 <http://www.gnu.org/licenses>. */
package app.fedilab.android.interfaces;
import app.fedilab.android.client.APIResponse;
public interface OnPostSubscription {
void onSubscription(APIResponse apiResponse);
}

View File

@ -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());
}
}

View File

@ -130,4 +130,13 @@
<color name="dark_icon_theme">#f3f3f3</color>
<color name="black_icon_theme">#606984</color>
<color name="rainbow_1">#ff0000</color>
<color name="rainbow_2">#ffa500</color>
<color name="rainbow_3">#ffff00</color>
<color name="rainbow_4">#008000</color>
<color name="rainbow_5">#0000ff</color>
<color name="rainbow_6">#4b0082</color>
<color name="rainbow_7">#ee82ee</color>
</resources>

View File

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