unifiedpush - first changes
This commit is contained in:
parent
c73fb06bd7
commit
f7702ecc12
|
@ -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
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
*
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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' }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue