diff --git a/app/build.gradle b/app/build.gradle
index 16879331..9f7c5795 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -61,4 +61,5 @@ dependencies {
implementation 'com.github.nuclearfog:Tagger:2.4'
implementation 'com.github.nuclearfog:LinkAndScrollMovement:1.4.1'
implementation 'com.github.kyleduo:SwitchButton:2.0.3-SNAPSHOT'
+ implementation 'com.github.UnifiedPush:android-connector:2.1.1'
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 70192d57..00bb8ff5 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -10,14 +10,11 @@
-->
-
-
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/java/org/nuclearfog/twidda/ClientApplication.java b/app/src/main/java/org/nuclearfog/twidda/ClientApplication.java
index 67b1955c..9e4fa868 100644
--- a/app/src/main/java/org/nuclearfog/twidda/ClientApplication.java
+++ b/app/src/main/java/org/nuclearfog/twidda/ClientApplication.java
@@ -4,12 +4,33 @@ import android.app.Application;
import org.nuclearfog.twidda.backend.image.ImageCache;
import org.nuclearfog.twidda.backend.image.PicassoBuilder;
+import org.unifiedpush.android.connector.ConstantsKt;
+import org.unifiedpush.android.connector.UnifiedPush;
+
+import java.util.ArrayList;
/**
* @author nuclearfog
*/
public class ClientApplication extends Application {
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ ArrayList features = new ArrayList<>(1);
+ features.add(UnifiedPush.FEATURE_BYTES_MESSAGE);
+ UnifiedPush.registerApp(this, ConstantsKt.INSTANCE_DEFAULT, features, "");
+ }
+
+
+ @Override
+ public void onTerminate() {
+ super.onTerminate();
+ UnifiedPush.unregisterApp(this, ConstantsKt.INSTANCE_DEFAULT);
+ }
+
+
@Override
public void onLowMemory() {
ImageCache.clear();
diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/api/Connection.java b/app/src/main/java/org/nuclearfog/twidda/backend/api/Connection.java
index 07e470a2..0b73f0a1 100644
--- a/app/src/main/java/org/nuclearfog/twidda/backend/api/Connection.java
+++ b/app/src/main/java/org/nuclearfog/twidda/backend/api/Connection.java
@@ -2,6 +2,7 @@ package org.nuclearfog.twidda.backend.api;
import org.nuclearfog.twidda.backend.helper.ConnectionConfig;
import org.nuclearfog.twidda.backend.helper.MediaStatus;
+import org.nuclearfog.twidda.backend.helper.update.PushUpdate;
import org.nuclearfog.twidda.lists.Domains;
import org.nuclearfog.twidda.lists.Messages;
import org.nuclearfog.twidda.backend.helper.update.ProfileUpdate;
@@ -24,6 +25,7 @@ import org.nuclearfog.twidda.model.Translation;
import org.nuclearfog.twidda.model.Trend;
import org.nuclearfog.twidda.model.User;
import org.nuclearfog.twidda.model.UserList;
+import org.nuclearfog.twidda.model.WebPush;
import java.util.List;
@@ -447,15 +449,6 @@ public interface Connection {
*/
void deleteStatus(long id) throws ConnectionException;
- /**
- * upload status with additional attachment
- *
- * @param update status update information
- * @param mediaIds IDs of the uploaded media files if any
- * @return uploaded status
- */
- Status uploadStatus(StatusUpdate update, List mediaIds) throws ConnectionException;
-
/**
* return a list of domain names the current user has blocked
*
@@ -478,6 +471,15 @@ public interface Connection {
*/
void unblockDomain(String domain) throws ConnectionException;
+ /**
+ * upload status with additional attachment
+ *
+ * @param update status update information
+ * @param mediaIds IDs of the uploaded media files if any
+ * @return uploaded status
+ */
+ Status updateStatus(StatusUpdate update, List mediaIds) throws ConnectionException;
+
/**
* create userlist
*
@@ -494,6 +496,30 @@ public interface Connection {
*/
UserList updateUserlist(UserListUpdate update) throws ConnectionException;
+ /**
+ * updates current user's profile
+ *
+ * @param update profile update information
+ * @return updated user information
+ */
+ User updateProfile(ProfileUpdate update) throws ConnectionException;
+
+ /**
+ * upload media file and generate a media ID
+ *
+ * @param mediaUpdate inputstream with MIME type of the media
+ * @return media ID
+ */
+ long updateMedia(MediaStatus mediaUpdate) throws ConnectionException;
+
+ /**
+ * create Web push subscription
+ *
+ * @param pushUpdate web push update
+ * @return created web push subscription
+ */
+ WebPush updatePush(PushUpdate pushUpdate) throws ConnectionException;
+
/**
* return userlist information
*
@@ -622,22 +648,6 @@ public interface Connection {
*/
MediaStatus downloadImage(String link) throws ConnectionException;
- /**
- * updates current user's profile
- *
- * @param update profile update information
- * @return updated user information
- */
- User updateProfile(ProfileUpdate update) throws ConnectionException;
-
- /**
- * upload media file and generate a media ID
- *
- * @param mediaUpdate inputstream with MIME type of the media
- * @return media ID
- */
- long uploadMedia(MediaStatus mediaUpdate) throws ConnectionException;
-
/**
* get notification of the current user
*
diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/Mastodon.java b/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/Mastodon.java
index a0aa6464..ddc9b631 100644
--- a/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/Mastodon.java
+++ b/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/Mastodon.java
@@ -1,6 +1,7 @@
package org.nuclearfog.twidda.backend.api.mastodon;
import android.content.Context;
+import android.util.Base64;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -17,6 +18,7 @@ import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonInstance;
import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonList;
import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonNotification;
import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonPoll;
+import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonPush;
import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonRelation;
import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonStatus;
import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonTranslation;
@@ -24,6 +26,7 @@ import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonTrend;
import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonUser;
import org.nuclearfog.twidda.backend.helper.ConnectionConfig;
import org.nuclearfog.twidda.backend.helper.MediaStatus;
+import org.nuclearfog.twidda.backend.helper.update.PushUpdate;
import org.nuclearfog.twidda.lists.Domains;
import org.nuclearfog.twidda.lists.Messages;
import org.nuclearfog.twidda.backend.helper.update.PollUpdate;
@@ -50,10 +53,19 @@ import org.nuclearfog.twidda.model.Translation;
import org.nuclearfog.twidda.model.Trend;
import org.nuclearfog.twidda.model.User;
import org.nuclearfog.twidda.model.UserList;
+import org.nuclearfog.twidda.model.WebPush;
import java.io.IOException;
import java.io.InputStream;
+import java.security.InvalidAlgorithmParameterException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.NoSuchAlgorithmException;
+import java.security.interfaces.ECPublicKey;
+import java.security.spec.ECGenParameterSpec;
+import java.security.spec.ECPoint;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
@@ -85,7 +97,7 @@ public class Mastodon implements Connection {
/**
* scopes used by this app
*/
- private static final String AUTH_SCOPES = "read%20write%20follow";
+ private static final String AUTH_SCOPES = "read%20write%20follow%20push";
/**
* oauth no redirect (oob)
@@ -126,6 +138,7 @@ public class Mastodon implements Connection {
private static final String ENDPOINT_CUSTOM_EMOJIS = "/api/v1/custom_emojis";
private static final String ENDPOINT_POLL = "/api/v1/polls/";
private static final String ENDPOINT_DOMAIN_BLOCK = "/api/v1/domain_blocks";
+ private static final String ENDPOINT_PUSH_UPDATE = "/api/v1/push/subscription";
private static final MediaType TYPE_TEXT = MediaType.parse("text/plain");
private static final MediaType TYPE_STREAM = MediaType.parse("application/octet-stream");
@@ -166,7 +179,7 @@ public class Mastodon implements Connection {
String client_id = json.getString("client_id");
String client_secret = json.getString("client_secret");
connection.setOauthTokens(client_id, client_secret);
- return hostname + ENDPOINT_AUTHORIZE_APP + "?scope=read%20write%20follow&response_type=code&redirect_uri=" + REDIRECT_URI + "&client_id=" + client_id;
+ return hostname + ENDPOINT_AUTHORIZE_APP + "?scope=" + AUTH_SCOPES + "&response_type=code&redirect_uri=" + REDIRECT_URI + "&client_id=" + client_id;
}
throw new MastodonException(response);
} catch (IOException | JSONException e) {
@@ -625,7 +638,7 @@ public class Mastodon implements Connection {
@Override
- public Status uploadStatus(StatusUpdate update, List mediaIds) throws MastodonException {
+ public Status updateStatus(StatusUpdate update, List mediaIds) throws MastodonException {
List params = new ArrayList<>();
// add identifier to prevent duplicate posts
params.add("Idempotency-Key=" + System.currentTimeMillis() / 5000);
@@ -972,7 +985,7 @@ public class Mastodon implements Connection {
@Override
- public long uploadMedia(MediaStatus mediaUpdate) throws MastodonException {
+ public long updateMedia(MediaStatus mediaUpdate) throws MastodonException {
try {
List params = new ArrayList<>();
if (!mediaUpdate.getDescription().isEmpty())
@@ -1004,6 +1017,65 @@ public class Mastodon implements Connection {
}
+ @Override
+ public WebPush updatePush(PushUpdate pushUpdate) throws ConnectionException {
+ try {
+ KeyPairGenerator generator = KeyPairGenerator.getInstance("EC");
+ ECGenParameterSpec spec = new ECGenParameterSpec("prime256v1");
+ generator.initialize(spec);
+ KeyPair keyPair = generator.generateKeyPair();
+ byte[] privKeyData = keyPair.getPrivate().getEncoded();
+ byte[] pubKeyData = keyPair.getPublic().getEncoded();
+ byte[] serializedPubKey = serializeRawPublicKey((ECPublicKey) keyPair.getPublic());
+ String encodedPublicKey = Base64.encodeToString(serializedPubKey, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
+ String pushPrivateKey = Base64.encodeToString(privKeyData, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
+ String pushPublicKey = Base64.encodeToString(pubKeyData, Base64.URL_SAFE | Base64.NO_WRAP | Base64.NO_PADDING);
+ String randomString = StringUtils.getRandomString();
+
+ List params = new ArrayList<>();
+ params.add("subscription[endpoint]=" + pushUpdate.getEndpoint());
+ params.add("subscription[keys][p256dh]=" + encodedPublicKey);
+ params.add("subscription[keys][auth]=" + randomString);
+ if (pushUpdate.enableMentions())
+ params.add("data[alerts][mention]=true");
+ if (pushUpdate.enableFavorite())
+ params.add("data[alerts][favourite]=true");
+ if (pushUpdate.enableRepost())
+ params.add("data[alerts][reblog]=true");
+ if (pushUpdate.enableFollow())
+ params.add("data[alerts][follow]=true");
+ if (pushUpdate.enableFollowRequest())
+ params.add("data[alerts][follow_request]=true");
+ if (pushUpdate.enablePoll())
+ params.add("data[alerts][poll]=true");
+ if (pushUpdate.enableStatus())
+ params.add("data[alerts][status]=true");
+ if (pushUpdate.enableStatusEdit())
+ params.add("data[alerts][update]=true");
+ if (pushUpdate.getPolicy() == PushUpdate.POLICY_ALL)
+ params.add("data[policy]=all");
+ else if (pushUpdate.getPolicy() == PushUpdate.POLICY_FOLLOWER)
+ params.add("data[policy]=follower");
+ else if (pushUpdate.getPolicy() == PushUpdate.POLICY_FOLLOWING)
+ params.add("data[policy]=followed");
+ else if (pushUpdate.getPolicy() == PushUpdate.POLICY_NONE)
+ params.add("data[policy]=none");
+ Response response = post(ENDPOINT_PUSH_UPDATE, params);
+ ResponseBody body = response.body();
+ if (response.code() == 200 && body != null) {
+ JSONObject json = new JSONObject(body.string());
+ MastodonPush result = new MastodonPush(json);
+ result.setKeys(pushPublicKey, pushPrivateKey);
+ result.setAuthSecret(randomString);
+ return result;
+ }
+ throw new MastodonException(response);
+ } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | JSONException | IOException e) {
+ throw new MastodonException(e);
+ }
+ }
+
+
@Override
public Notifications getNotifications(long minId, long maxId) throws ConnectionException {
List params = new ArrayList<>();
@@ -1624,4 +1696,22 @@ public class Mastodon implements Connection {
}
return hostname + endpoint;
}
+
+ /**
+ *
+ */
+ private byte[] serializeRawPublicKey(ECPublicKey key) {
+ ECPoint point = key.getW();
+ byte[] x = point.getAffineX().toByteArray();
+ byte[] y = point.getAffineY().toByteArray();
+ if(x.length>32)
+ x = Arrays.copyOfRange(x, x.length-32, x.length);
+ if(y.length>32)
+ y = Arrays.copyOfRange(y, y.length-32, y.length);
+ byte[] result = new byte[65];
+ result[0] = 4;
+ System.arraycopy(x, 0, result, 1+(32-x.length), x.length);
+ System.arraycopy(y, 0, result, result.length-y.length, y.length);
+ return result;
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/impl/MastodonPush.java b/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/impl/MastodonPush.java
new file mode 100644
index 00000000..ffc42b6c
--- /dev/null
+++ b/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/impl/MastodonPush.java
@@ -0,0 +1,151 @@
+package org.nuclearfog.twidda.backend.api.mastodon.impl;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.nuclearfog.twidda.model.WebPush;
+
+/**
+ * Mastodon push implementation
+ *
+ * @author nuclearfog
+ */
+public class MastodonPush implements WebPush {
+
+ private static final long serialVersionUID = 565081495547561476L;
+
+ private long id;
+ private String endpoint;
+ private String serverKey, publicKey, privateKey, authSec;
+
+ /**
+ * @param json web push json object
+ */
+ public MastodonPush(JSONObject json) throws JSONException {
+ String id = json.getString("id");
+ endpoint = json.getString("endpoint");
+ serverKey = json.getString("server_key");
+ try {
+ this.id = Long.parseLong(id);
+ } catch (NumberFormatException e) {
+ throw new JSONException("bad ID: " + id);
+ }
+ }
+
+
+ @Override
+ public long getId() {
+ return id;
+ }
+
+
+ @Override
+ public String getEndpoint() {
+ return endpoint;
+ }
+
+
+ @Override
+ public String getServerKey() {
+ return serverKey;
+ }
+
+
+ @Override
+ public String getPublicKey() {
+ return publicKey;
+ }
+
+
+ @Override
+ public String getPrivateKey() {
+ return privateKey;
+ }
+
+
+ @Override
+ public String getAuthSecret() {
+ return authSec;
+ }
+
+
+ @Override
+ public boolean alertMentionEnabled() {
+ return false;
+ }
+
+
+ @Override
+ public boolean alertStatusEnabled() {
+ return false;
+ }
+
+
+ @Override
+ public boolean alertRepostEnabled() {
+ return false;
+ }
+
+
+ @Override
+ public boolean alertFollowingEnabled() {
+ return false;
+ }
+
+
+ @Override
+ public boolean alertFollowRequestEnabled() {
+ return false;
+ }
+
+
+ @Override
+ public boolean alertFavoriteEnabled() {
+ return false;
+ }
+
+
+ @Override
+ public boolean alertPollEnabled() {
+ return false;
+ }
+
+
+ @Override
+ public boolean alertStatusChangeEnabled() {
+ return false;
+ }
+
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "id=" + getId() + " url=\"" + getEndpoint() + "\"";
+ }
+
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof WebPush))
+ return false;
+ WebPush push = (WebPush) obj;
+ return getId() == push.getId() && getEndpoint().equals(push.getEndpoint());
+ }
+
+ /**
+ * set encryption keys
+ */
+ public void setKeys(String publicKey, String privateKey) {
+ this.publicKey = publicKey;
+ this.privateKey = privateKey;
+ }
+
+ /**
+ * set auth key
+ */
+ public void setAuthSecret(String authSec) {
+ this.authSec = authSec;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/api/twitter/v1/TwitterV1.java b/app/src/main/java/org/nuclearfog/twidda/backend/api/twitter/v1/TwitterV1.java
index 7dd18d9d..9ad06ca3 100644
--- a/app/src/main/java/org/nuclearfog/twidda/backend/api/twitter/v1/TwitterV1.java
+++ b/app/src/main/java/org/nuclearfog/twidda/backend/api/twitter/v1/TwitterV1.java
@@ -23,6 +23,7 @@ import org.nuclearfog.twidda.backend.api.twitter.v1.impl.UserListV1;
import org.nuclearfog.twidda.backend.api.twitter.v1.impl.UserV1;
import org.nuclearfog.twidda.backend.helper.ConnectionConfig;
import org.nuclearfog.twidda.backend.helper.MediaStatus;
+import org.nuclearfog.twidda.backend.helper.update.PushUpdate;
import org.nuclearfog.twidda.lists.Domains;
import org.nuclearfog.twidda.lists.Messages;
import org.nuclearfog.twidda.backend.helper.update.ProfileUpdate;
@@ -49,6 +50,7 @@ import org.nuclearfog.twidda.model.Translation;
import org.nuclearfog.twidda.model.Trend;
import org.nuclearfog.twidda.model.User;
import org.nuclearfog.twidda.model.UserList;
+import org.nuclearfog.twidda.model.WebPush;
import java.io.IOException;
import java.io.InputStream;
@@ -746,7 +748,7 @@ public class TwitterV1 implements Connection {
@Override
- public Status uploadStatus(StatusUpdate update, List mediaIds) throws TwitterException {
+ public Status updateStatus(StatusUpdate update, List mediaIds) throws TwitterException {
List params = new ArrayList<>();
if (update.getText() != null)
params.add("status=" + StringUtils.encode(update.getText()));
@@ -1012,7 +1014,7 @@ public class TwitterV1 implements Connection {
@Override
- public long uploadMedia(MediaStatus mediaUpdate) throws TwitterException {
+ public long updateMedia(MediaStatus mediaUpdate) throws TwitterException {
List params = new ArrayList<>();
boolean enableChunk;
final long mediaId;
@@ -1096,6 +1098,12 @@ public class TwitterV1 implements Connection {
}
+ @Override
+ public WebPush updatePush(PushUpdate pushUpdate) throws TwitterException {
+ throw new TwitterException("not implemented");
+ }
+
+
@Override
public MediaStatus downloadImage(String link) throws TwitterException {
try {
diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/async/MessageUpdater.java b/app/src/main/java/org/nuclearfog/twidda/backend/async/MessageUpdater.java
index b56c1232..10a1937e 100644
--- a/app/src/main/java/org/nuclearfog/twidda/backend/async/MessageUpdater.java
+++ b/app/src/main/java/org/nuclearfog/twidda/backend/async/MessageUpdater.java
@@ -37,7 +37,7 @@ public class MessageUpdater extends AsyncExecutor {
+
+ private Connection connection;
+ private GlobalSettings settings;
+
+ /**
+ *
+ */
+ public PushUpdater(Context context) {
+ connection = ConnectionManager.getDefaultConnection(context);
+ settings = GlobalSettings.getInstance(context);
+ }
+
+
+ @Override
+ protected Void doInBackground(@NonNull PushUpdate param) {
+ try {
+ WebPush webpush = connection.updatePush(param);
+ settings.setWebPush(webpush);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/async/StatusUpdater.java b/app/src/main/java/org/nuclearfog/twidda/backend/async/StatusUpdater.java
index ea6abe85..c7f012dd 100644
--- a/app/src/main/java/org/nuclearfog/twidda/backend/async/StatusUpdater.java
+++ b/app/src/main/java/org/nuclearfog/twidda/backend/async/StatusUpdater.java
@@ -41,12 +41,12 @@ public class StatusUpdater extends AsyncExecutor mediaIds = new LinkedList<>();
for (MediaStatus mediaStatus : update.getMediaStatuses()) {
if (mediaStatus.isLocal()) {
- long mediaId = connection.uploadMedia(mediaStatus);
+ long mediaId = connection.updateMedia(mediaStatus);
mediaIds.add(mediaId);
}
}
// upload status
- Status status = connection.uploadStatus(update, mediaIds);
+ Status status = connection.updateStatus(update, mediaIds);
return new StatusUpdateResult(status, null);
} catch (ConnectionException exception) {
return new StatusUpdateResult(null, exception);
diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/helper/MediaStatus.java b/app/src/main/java/org/nuclearfog/twidda/backend/helper/MediaStatus.java
index 87a22a2b..dce48986 100644
--- a/app/src/main/java/org/nuclearfog/twidda/backend/helper/MediaStatus.java
+++ b/app/src/main/java/org/nuclearfog/twidda/backend/helper/MediaStatus.java
@@ -6,6 +6,7 @@ import android.net.Uri;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.Serializable;
@@ -15,7 +16,7 @@ import java.io.Serializable;
*
* @author nuclearfog
*/
-public class MediaStatus implements Serializable {
+public class MediaStatus implements Serializable, Closeable {
private static final long serialVersionUID = 6824278073662885637L;
@@ -50,6 +51,20 @@ public class MediaStatus implements Serializable {
local = true;
}
+ /**
+ * close stream
+ */
+ @Override
+ public void close() {
+ try {
+ if (inputStream != null) {
+ inputStream.close();
+ }
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+
/**
* create a stream to upload media file
*
@@ -113,20 +128,6 @@ public class MediaStatus implements Serializable {
return local;
}
- /**
- * close stream
- */
- public void close() {
- try {
- if (inputStream != null) {
- inputStream.close();
- }
- } catch (IOException e) {
- // ignore
- }
- }
-
-
@NonNull
@Override
public String toString() {
diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/helper/update/MessageUpdate.java b/app/src/main/java/org/nuclearfog/twidda/backend/helper/update/MessageUpdate.java
index 116277f0..73d7eb7d 100644
--- a/app/src/main/java/org/nuclearfog/twidda/backend/helper/update/MessageUpdate.java
+++ b/app/src/main/java/org/nuclearfog/twidda/backend/helper/update/MessageUpdate.java
@@ -11,6 +11,7 @@ import androidx.documentfile.provider.DocumentFile;
import org.nuclearfog.twidda.backend.helper.MediaStatus;
import org.nuclearfog.twidda.model.Instance;
+import java.io.Closeable;
import java.io.Serializable;
import java.util.Arrays;
import java.util.TreeSet;
@@ -20,7 +21,7 @@ import java.util.TreeSet;
*
* @author nuclearfog
*/
-public class MessageUpdate implements Serializable {
+public class MessageUpdate implements Serializable, Closeable {
private static final long serialVersionUID = 991295406939128220L;
@@ -36,6 +37,16 @@ public class MessageUpdate implements Serializable {
private TreeSet supportedFormats = new TreeSet<>();
+ /**
+ * close inputstream of media file
+ */
+ @Override
+ public void close() {
+ if (mediaUpdate != null) {
+ mediaUpdate.close();
+ }
+ }
+
/**
* @param name screen name of the user
*/
@@ -135,14 +146,6 @@ public class MessageUpdate implements Serializable {
return instance;
}
- /**
- * close inputstream of media file
- */
- public void close() {
- if (mediaUpdate != null) {
- mediaUpdate.close();
- }
- }
@NonNull
@Override
diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/helper/update/ProfileUpdate.java b/app/src/main/java/org/nuclearfog/twidda/backend/helper/update/ProfileUpdate.java
index c240c728..e74dd8e2 100644
--- a/app/src/main/java/org/nuclearfog/twidda/backend/helper/update/ProfileUpdate.java
+++ b/app/src/main/java/org/nuclearfog/twidda/backend/helper/update/ProfileUpdate.java
@@ -9,6 +9,7 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.documentfile.provider.DocumentFile;
+import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
@@ -17,7 +18,7 @@ import java.io.InputStream;
*
* @author nuclearfog
*/
-public class ProfileUpdate {
+public class ProfileUpdate implements Closeable {
private Uri[] imageUrls = new Uri[2];
private InputStream[] imageStreams = new InputStream[2];
@@ -27,6 +28,23 @@ public class ProfileUpdate {
private String description = "";
private String location = "";
+
+ /**
+ * close all image streams
+ */
+ @Override
+ public void close() {
+ try {
+ for (InputStream imageStream : imageStreams) {
+ if (imageStream != null) {
+ imageStream.close();
+ }
+ }
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+
/**
* setup profile information
*
@@ -151,21 +169,6 @@ public class ProfileUpdate {
return true;
}
- /**
- * close all image streams
- */
- public void close() {
- try {
- for (InputStream imageStream : imageStreams) {
- if (imageStream != null) {
- imageStream.close();
- }
- }
- } catch (IOException e) {
- // ignore
- }
- }
-
@NonNull
@Override
diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/helper/update/PushUpdate.java b/app/src/main/java/org/nuclearfog/twidda/backend/helper/update/PushUpdate.java
new file mode 100644
index 00000000..20b12abc
--- /dev/null
+++ b/app/src/main/java/org/nuclearfog/twidda/backend/helper/update/PushUpdate.java
@@ -0,0 +1,101 @@
+package org.nuclearfog.twidda.backend.helper.update;
+
+import java.io.Serializable;
+
+/**
+ * Webpush updater class used to create a webpush subscription
+ * @see org.nuclearfog.twidda.backend.api.Connection
+ *
+ * @author nuclearfog
+ */
+public class PushUpdate implements Serializable {
+
+ private static final long serialVersionUID = -34599486422177957L;
+
+ /**
+ * show all notifications
+ */
+ public static final int POLICY_ALL = 1;
+
+ /**
+ * show only notifications of followed users
+ */
+ public static final int POLICY_FOLLOWING = 2;
+
+ /**
+ * show only notifications of followers
+ */
+ public static final int POLICY_FOLLOWER = 3;
+
+ /**
+ * disable push notification
+ */
+ public static final int POLICY_NONE = 4;
+
+ private String endpoint;
+ private boolean notifyMention, notifyStatus, notifyFollow, notifyFollowRequest;
+ private boolean notifyFavorite, notifyRepost, notifyPoll, notifyEdit;
+ private int policy;
+
+ /**
+ *
+ */
+ public PushUpdate(String endpoint) {
+ int idx = endpoint.indexOf('?');
+ if (idx > 0) {
+ this.endpoint = endpoint.substring(0, idx);
+ } else {
+ this.endpoint = endpoint;
+ }
+ }
+
+
+ public String getEndpoint() {
+ return endpoint;
+ }
+
+
+ public boolean enableMentions() {
+ return notifyMention;
+ }
+
+
+ public boolean enableStatus() {
+ return notifyStatus;
+ }
+
+
+ public boolean enableStatusEdit() {
+ return notifyEdit;
+ }
+
+
+ public boolean enableRepost() {
+ return notifyRepost;
+ }
+
+
+ public boolean enableFavorite() {
+ return notifyFavorite;
+ }
+
+
+ public boolean enablePoll() {
+ return notifyPoll;
+ }
+
+
+ public boolean enableFollow() {
+ return notifyFollow;
+ }
+
+
+ public boolean enableFollowRequest() {
+ return notifyFollowRequest;
+ }
+
+
+ public int getPolicy() {
+ return policy;
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/helper/update/StatusUpdate.java b/app/src/main/java/org/nuclearfog/twidda/backend/helper/update/StatusUpdate.java
index d840516e..12b656b7 100644
--- a/app/src/main/java/org/nuclearfog/twidda/backend/helper/update/StatusUpdate.java
+++ b/app/src/main/java/org/nuclearfog/twidda/backend/helper/update/StatusUpdate.java
@@ -14,6 +14,7 @@ import org.nuclearfog.twidda.model.Instance;
import org.nuclearfog.twidda.model.Media;
import org.nuclearfog.twidda.model.Status;
+import java.io.Closeable;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
@@ -25,7 +26,7 @@ import java.util.TreeSet;
*
* @author nuclearfog
*/
-public class StatusUpdate implements Serializable {
+public class StatusUpdate implements Serializable, Closeable {
private static final long serialVersionUID = -5300983806882462557L;
@@ -93,6 +94,18 @@ public class StatusUpdate implements Serializable {
private boolean attachmentLimitReached = false;
private int attachment = EMPTY;
+ /**
+ * close all open streams
+ */
+ @Override
+ public void close() {
+ for (MediaStatus mediaUpdate : mediaStatuses) {
+ if (mediaUpdate != null) {
+ mediaUpdate.close();
+ }
+ }
+ }
+
/**
* set informations of an existing status to edit these
*
@@ -465,17 +478,6 @@ public class StatusUpdate implements Serializable {
return true;
}
- /**
- * close all open streams
- */
- public void close() {
- for (MediaStatus mediaUpdate : mediaStatuses) {
- if (mediaUpdate != null) {
- mediaUpdate.close();
- }
- }
- }
-
@NonNull
@Override
diff --git a/app/src/main/java/org/nuclearfog/twidda/config/GlobalSettings.java b/app/src/main/java/org/nuclearfog/twidda/config/GlobalSettings.java
index 2be24809..b4cbe7d3 100644
--- a/app/src/main/java/org/nuclearfog/twidda/config/GlobalSettings.java
+++ b/app/src/main/java/org/nuclearfog/twidda/config/GlobalSettings.java
@@ -12,8 +12,10 @@ import androidx.annotation.Nullable;
import org.nuclearfog.twidda.config.impl.ConfigAccount;
import org.nuclearfog.twidda.config.impl.ConfigLocation;
+import org.nuclearfog.twidda.config.impl.ConfigPush;
import org.nuclearfog.twidda.model.Account;
import org.nuclearfog.twidda.model.Location;
+import org.nuclearfog.twidda.model.WebPush;
import java.util.LinkedList;
import java.util.List;
@@ -85,6 +87,12 @@ public class GlobalSettings {
private static final String PROXY_PASS = "proxy_pass";
private static final String TREND_LOC = "location";
private static final String TREND_ID = "world_id_long";
+ private static final String PUSH_ID = "push_id";
+ private static final String PUSH_SERVER_HOST = "push_server_host";
+ private static final String PUSH_SERVER_KEY = "push_server_key";
+ private static final String PUSH_PUBLIC_KEY = "push_public_key";
+ private static final String PUSH_PRIVATE_KEY = "push_private_key";
+ private static final String PUSH_AUTH_KEY = "push_auth_key";
private static final String ENABLE_LIKE = "like_enable";
private static final String ENABLE_TWITTER_ALT = "twitter_alt_set";
private static final String FILTER_RESULTS = "filter_results";
@@ -125,6 +133,7 @@ public class GlobalSettings {
private SharedPreferences settings;
private Location location;
+ private ConfigPush webPush;
private ConfigAccount login;
private String proxyHost, proxyPort;
private String proxyUser, proxyPass;
@@ -547,6 +556,31 @@ public class GlobalSettings {
edit.apply();
}
+ /**
+ * get used web push instance
+ */
+ public WebPush getWebPush() {
+ return webPush;
+ }
+
+ /**
+ * save web push configuration
+ *
+ * @param webPush web push information
+ */
+ public void setWebPush(WebPush webPush) {
+ this.webPush = new ConfigPush(webPush);
+
+ Editor edit = settings.edit();
+ edit.putLong(PUSH_ID, webPush.getId());
+ edit.putString(PUSH_SERVER_KEY, webPush.getServerKey());
+ edit.putString(PUSH_SERVER_HOST, webPush.getEndpoint());
+ edit.putString(PUSH_PUBLIC_KEY, webPush.getPublicKey());
+ edit.putString(PUSH_PRIVATE_KEY, webPush.getPrivateKey());
+ edit.putString(PUSH_AUTH_KEY, webPush.getAuthSecret());
+ edit.apply();
+ }
+
/**
* get loading limit of tweets/users
@@ -967,7 +1001,14 @@ public class GlobalSettings {
proxyPass = settings.getString(PROXY_PASS, "");
String place = settings.getString(TREND_LOC, DEFAULT_LOCATION_NAME);
long woeId = settings.getLong(TREND_ID, DEFAULT_LOCATION_ID);
+ long pushID = settings.getLong(PUSH_ID, 0L);
+ String pushServerKey = settings.getString(PUSH_SERVER_KEY, "");
+ String pushServerHost = settings.getString(PUSH_SERVER_HOST, "");
+ String pushPublicKey = settings.getString(PUSH_PUBLIC_KEY, "");
+ String pushPrivateKey = settings.getString(PUSH_PRIVATE_KEY, "");
+ String pushAuthKey = settings.getString(PUSH_AUTH_KEY, "");
location = new ConfigLocation(woeId, place);
+ webPush = new ConfigPush(pushID, pushServerHost, pushServerKey, pushPublicKey, pushPrivateKey, pushAuthKey);
// login informations
initLogin();
}
diff --git a/app/src/main/java/org/nuclearfog/twidda/config/impl/ConfigPush.java b/app/src/main/java/org/nuclearfog/twidda/config/impl/ConfigPush.java
new file mode 100644
index 00000000..c7fb57a5
--- /dev/null
+++ b/app/src/main/java/org/nuclearfog/twidda/config/impl/ConfigPush.java
@@ -0,0 +1,142 @@
+package org.nuclearfog.twidda.config.impl;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import org.nuclearfog.twidda.model.WebPush;
+
+/**
+ * @author nuclearfog
+ */
+public class ConfigPush implements WebPush {
+
+ private static final long serialVersionUID = -6942479639448210795L;
+
+ private long id;
+ private String endpoint;
+ private String serverKey, publicKey, privateKey, authKey;
+
+ /**
+ * @param webPush web push instance to copy information
+ */
+ public ConfigPush(WebPush webPush) {
+ id = webPush.getId();
+ endpoint = webPush.getEndpoint();
+ serverKey = webPush.getServerKey();
+ publicKey = webPush.getPublicKey();
+ privateKey = webPush.getPrivateKey();
+ authKey = webPush.getAuthSecret();
+ }
+
+ /**
+ *
+ */
+ public ConfigPush(long id, String endpoint, String serverKey, String publicKey, String privateKey, String authKey) {
+ this.id = id;
+ this.endpoint = endpoint;
+ this.serverKey = serverKey;
+ this.privateKey = privateKey;
+ this.publicKey = publicKey;
+ this.authKey = authKey;
+ }
+
+
+ @Override
+ public long getId() {
+ return id;
+ }
+
+
+ @Override
+ public String getEndpoint() {
+ return endpoint;
+ }
+
+
+ @Override
+ public String getServerKey() {
+ return serverKey;
+ }
+
+
+ @Override
+ public String getPublicKey() {
+ return publicKey;
+ }
+
+
+ @Override
+ public String getPrivateKey() {
+ return privateKey;
+ }
+
+
+ @Override
+ public String getAuthSecret() {
+ return authKey;
+ }
+
+
+ @Override
+ public boolean alertMentionEnabled() {
+ return false;
+ }
+
+
+ @Override
+ public boolean alertStatusEnabled() {
+ return false;
+ }
+
+
+ @Override
+ public boolean alertRepostEnabled() {
+ return false;
+ }
+
+
+ @Override
+ public boolean alertFollowingEnabled() {
+ return false;
+ }
+
+
+ @Override
+ public boolean alertFollowRequestEnabled() {
+ return false;
+ }
+
+
+ @Override
+ public boolean alertFavoriteEnabled() {
+ return false;
+ }
+
+
+ @Override
+ public boolean alertPollEnabled() {
+ return false;
+ }
+
+
+ @Override
+ public boolean alertStatusChangeEnabled() {
+ return false;
+ }
+
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "id=" + getId() + " url=\"" + getEndpoint() + "\"";
+ }
+
+
+ @Override
+ public boolean equals(@Nullable Object obj) {
+ if (!(obj instanceof WebPush))
+ return false;
+ WebPush push = (WebPush) obj;
+ return getId() == push.getId() && getEndpoint().equals(push.getEndpoint());
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/nuclearfog/twidda/model/WebPush.java b/app/src/main/java/org/nuclearfog/twidda/model/WebPush.java
new file mode 100644
index 00000000..73a9d2a6
--- /dev/null
+++ b/app/src/main/java/org/nuclearfog/twidda/model/WebPush.java
@@ -0,0 +1,81 @@
+package org.nuclearfog.twidda.model;
+
+import java.io.Serializable;
+
+/**
+ * Represents a web push subscription.
+ *
+ * @author nuclearfog
+ */
+public interface WebPush extends Serializable {
+
+ /**
+ * @return ID of the subscription
+ */
+ long getId();
+
+ /**
+ * @return webpush host url
+ */
+ String getEndpoint();
+
+ /**
+ * @return unique server key set from {@link org.nuclearfog.twidda.backend.api.Connection}
+ */
+ String getServerKey();
+
+ /**
+ * @return encryption public key
+ */
+ String getPublicKey();
+
+ /**
+ * @return encryption public key
+ */
+ String getPrivateKey();
+
+ /**
+ * @return auth secret
+ */
+ String getAuthSecret();
+
+ /**
+ * @return true if notification for mentions is enabled
+ */
+ boolean alertMentionEnabled();
+
+ /**
+ * @return true if status notification (profile subscription) is enabled
+ */
+ boolean alertStatusEnabled();
+
+ /**
+ * @return true if 'status reposted' notification is enabled
+ */
+ boolean alertRepostEnabled();
+
+ /**
+ * @return true if 'new follower' notification is enabled
+ */
+ boolean alertFollowingEnabled();
+
+ /**
+ * @return true if 'follow request' notification is enabled
+ */
+ boolean alertFollowRequestEnabled();
+
+ /**
+ * @return true if 'status favorited' notification is enabled
+ */
+ boolean alertFavoriteEnabled();
+
+ /**
+ * @return true if 'poll finished' notification is enabled
+ */
+ boolean alertPollEnabled();
+
+ /**
+ * @return true if 'status changed' notification is enabled
+ */
+ boolean alertStatusChangeEnabled();
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/nuclearfog/twidda/receiver/PushNotificationReceiver.java b/app/src/main/java/org/nuclearfog/twidda/receiver/PushNotificationReceiver.java
new file mode 100644
index 00000000..8fd88b84
--- /dev/null
+++ b/app/src/main/java/org/nuclearfog/twidda/receiver/PushNotificationReceiver.java
@@ -0,0 +1,33 @@
+package org.nuclearfog.twidda.receiver;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+
+import org.nuclearfog.twidda.backend.async.PushUpdater;
+import org.nuclearfog.twidda.backend.helper.update.PushUpdate;
+import org.unifiedpush.android.connector.MessagingReceiver;
+
+/**
+ * Push notification receiver used to trigger synchronization.
+ *
+ * @author nuclearfog
+ */
+public class PushNotificationReceiver extends MessagingReceiver {
+
+
+ @Override
+ public void onMessage(@NonNull Context context, @NonNull byte[] message, @NonNull String instance) {
+ super.onMessage(context, message, instance);
+ // todo add manual synchonization
+ }
+
+
+ @Override
+ public void onNewEndpoint(@NonNull Context context, @NonNull String endpoint, @NonNull String instance) {
+ super.onNewEndpoint(context, endpoint, instance);
+ PushUpdater pushUpdater = new PushUpdater(context);
+ PushUpdate update = new PushUpdate(endpoint);
+ pushUpdater.execute(update, null);
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/org/nuclearfog/twidda/ui/activities/UserlistActivity.java b/app/src/main/java/org/nuclearfog/twidda/ui/activities/UserlistActivity.java
index 765acd6a..d295cf57 100644
--- a/app/src/main/java/org/nuclearfog/twidda/ui/activities/UserlistActivity.java
+++ b/app/src/main/java/org/nuclearfog/twidda/ui/activities/UserlistActivity.java
@@ -76,7 +76,7 @@ public class UserlistActivity extends AppCompatActivity implements ActivityResul
* regex pattern to validate username
* e.g. username, @username or @username@instance.social
*/
- private static final Pattern USERNAME_PATTERN = Pattern.compile("@?[\\w\\d]{1,20}(@[\\w\\d.]{1,50})?");
+ private static final Pattern USERNAME_PATTERN = Pattern.compile("@?\\w{1,20}(@[\\w.]{1,50})?");
private ActivityResultLauncher activityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), this);
diff --git a/app/src/main/java/org/nuclearfog/twidda/ui/views/AnimatedImageView.java b/app/src/main/java/org/nuclearfog/twidda/ui/views/AnimatedImageView.java
index c3a588c0..36523a61 100644
--- a/app/src/main/java/org/nuclearfog/twidda/ui/views/AnimatedImageView.java
+++ b/app/src/main/java/org/nuclearfog/twidda/ui/views/AnimatedImageView.java
@@ -48,6 +48,7 @@ public class AnimatedImageView extends AppCompatImageView {
* @inheritDoc
*/
@Override
+ @SuppressWarnings("deprecation")
public void setImageURI(@Nullable Uri uri) {
ContentResolver resolver = getContext().getContentResolver();
String mime = resolver.getType(uri);
@@ -66,6 +67,7 @@ public class AnimatedImageView extends AppCompatImageView {
* @inheritDoc
*/
@Override
+ @SuppressWarnings("deprecation")
protected void onDraw(Canvas canvas) {
if (movie != null) {
// calculate scale and offsets