added push notification support, memory leak fix

This commit is contained in:
nuclearfog 2023-05-29 23:28:11 +02:00
parent 1294539a62
commit 115029f275
No known key found for this signature in database
GPG Key ID: 03488A185C476379
14 changed files with 233 additions and 27 deletions

View File

@ -15,7 +15,7 @@
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="29" tools:ignore="ScopedStorage" />
<uses-permission android:name="io.heckel.ntfy.SEND_MESSAGE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<application
android:name=".ClientApplication"

View File

@ -1,10 +1,16 @@
package org.nuclearfog.twidda;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import android.app.Application;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.os.Build;
import org.nuclearfog.twidda.backend.image.ImageCache;
import org.nuclearfog.twidda.backend.image.PicassoBuilder;
import org.nuclearfog.twidda.config.GlobalSettings;
import org.nuclearfog.twidda.notification.PushNotification;
import org.nuclearfog.twidda.notification.PushSubscription;
/**
@ -14,13 +20,21 @@ public class ClientApplication extends Application {
private GlobalSettings settings;
@Override
public void onCreate() {
super.onCreate();
// setup push receiver
settings = GlobalSettings.getInstance(getApplicationContext());
if (settings.pushEnabled()) {
PushSubscription.subscripe(getApplicationContext());
}
// setup notification channel
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager manager = getSystemService(NotificationManager.class);
NotificationChannel channel = new NotificationChannel(PushNotification.NOTIFICATION_ID_STR, "fdgdfg", IMPORTANCE_LOW);
manager.createNotificationChannel(channel);
}
}

View File

@ -10,6 +10,7 @@ import org.nuclearfog.twidda.backend.api.Connection;
import org.nuclearfog.twidda.backend.api.ConnectionException;
import org.nuclearfog.twidda.backend.api.ConnectionManager;
import org.nuclearfog.twidda.database.AppDatabase;
import org.nuclearfog.twidda.model.Notification;
import org.nuclearfog.twidda.model.lists.Notifications;
import org.nuclearfog.twidda.ui.fragments.NotificationFragment;
@ -33,24 +34,38 @@ public class NotificationLoader extends AsyncExecutor<NotificationLoader.Notific
@Override
protected NotificationLoaderResult doInBackground(@NonNull NotificationLoaderParam params) {
protected NotificationLoaderResult doInBackground(@NonNull NotificationLoaderParam param) {
try {
Notifications result;
if (params.minId == 0L && params.maxId == 0L) {
result = db.getNotifications();
if (result.isEmpty()) {
result = connection.getNotifications(0L, 0L);
db.saveNotifications(result);
}
} else {
result = connection.getNotifications(params.minId, params.maxId);
if (params.maxId == 0L) {
db.saveNotifications(result);
}
switch (param.mode) {
case NotificationLoaderParam.LOAD_ALL:
Notifications result;
if (param.minId == 0L && param.maxId == 0L) {
result = db.getNotifications();
if (result.isEmpty()) {
result = connection.getNotifications(0L, 0L);
db.saveNotifications(result);
}
} else {
result = connection.getNotifications(param.minId, param.maxId);
if (param.maxId == 0L) {
db.saveNotifications(result);
}
}
return new NotificationLoaderResult(result, param.position, null);
case NotificationLoaderParam.LOAD_UNREAD:
// load (known) notifications from database first
Notifications notifications = db.getNotifications();
// then load new notifications using the latest known notification
long minId = 0L;
Notification lastNotification = notifications.peekFirst();
if (lastNotification != null)
minId = lastNotification.getId();
result = connection.getNotifications(minId, 0L);
return new NotificationLoaderResult(result, 0, null);
}
return new NotificationLoaderResult(result, params.position, null);
} catch (ConnectionException exception) {
return new NotificationLoaderResult(null, params.position, exception);
return new NotificationLoaderResult(null, param.position, exception);
} catch (Exception exception) {
if (BuildConfig.DEBUG) {
exception.printStackTrace();
@ -64,13 +79,18 @@ public class NotificationLoader extends AsyncExecutor<NotificationLoader.Notific
*/
public static class NotificationLoaderParam {
public static final int LOAD_ALL = 1;
public static final int LOAD_UNREAD = 2;
final int position;
final long minId, maxId;
final int mode;
public NotificationLoaderParam(int position, long minId, long maxId) {
public NotificationLoaderParam(int mode, int position, long minId, long maxId) {
this.position = position;
this.minId = minId;
this.maxId = maxId;
this.mode = mode;
}
}

View File

@ -23,7 +23,12 @@ public class PushUpdate implements Serializable {
* @param host unifiedpush host url
*/
public PushUpdate(String host) {
this.host = host;
int idxQuery = host.indexOf("?");
if (idxQuery > 0) {
this.host = host.substring(0, idxQuery);
} else {
this.host = host;
}
}
/**

View File

@ -0,0 +1,107 @@
package org.nuclearfog.twidda.notification;
import android.annotation.SuppressLint;
import android.content.Context;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import org.nuclearfog.twidda.BuildConfig;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.config.GlobalSettings;
import org.nuclearfog.twidda.model.Notification;
import org.nuclearfog.twidda.model.lists.Notifications;
/**
* This class creates app push notification
*
* @author nuclearfog
*/
public class PushNotification {
public static final String NOTIFICATION_ID_STR = BuildConfig.APPLICATION_ID + ".notification";
private static final int NOTIFICATION_ID = 0x25281;
private NotificationManagerCompat notificationManager;
private NotificationCompat.Builder notificationBuilder;
private GlobalSettings settings;
private Context context;
/**
*
*/
public PushNotification(Context context) {
notificationManager = NotificationManagerCompat.from(context);
notificationBuilder = new NotificationCompat.Builder(context, NOTIFICATION_ID_STR);
settings = GlobalSettings.getInstance(context);
this.context = context;
}
/**
* create push-notification from notifications
* @param notifications new notifications
*/
@SuppressLint("MissingPermission")
public void createNotification(Notifications notifications) {
if (!notifications.isEmpty()) {
String title = settings.getLogin().getConfiguration().getName();
String content;
int icon;
if (notifications.size() > 1) {
content = context.getString(R.string.notification_new);
icon = R.drawable.bell;
} else {
Notification notification = notifications.getFirst();
switch (notification.getType()) {
case Notification.TYPE_FAVORITE:
icon = R.drawable.favorite;
content = context.getString(R.string.notification_favorite, notification.getUser().getScreenname());
break;
case Notification.TYPE_REPOST:
icon = R.drawable.repost;
content = context.getString(R.string.notification_repost, notification.getUser().getScreenname());
break;
case Notification.TYPE_FOLLOW:
icon = R.drawable.follower;
content = context.getString(R.string.notification_follow, notification.getUser().getScreenname());
break;
case Notification.TYPE_REQUEST:
icon = R.drawable.follower_request;
content = context.getString(R.string.notification_request, notification.getUser().getScreenname());
break;
case Notification.TYPE_MENTION:
icon = R.drawable.mention;
content = context.getString(R.string.notification_mention, notification.getUser().getScreenname());
break;
case Notification.TYPE_STATUS:
icon = R.drawable.post;
content = context.getString(R.string.notification_status, notification.getUser().getScreenname());
break;
case Notification.TYPE_UPDATE:
icon = R.drawable.post;
content = context.getString(R.string.notification_edit);
break;
case Notification.TYPE_POLL:
icon = R.drawable.poll;
content = context.getString(R.string.notification_poll);
break;
default:
icon = R.drawable.bell;
content = context.getString(R.string.notification_new);
break;
}
}
notificationBuilder.setContentTitle(title).setContentText(content).setSmallIcon(icon).setAutoCancel(true);
notificationManager.notify(NOTIFICATION_ID, notificationBuilder.build());
}
}
}

View File

@ -4,6 +4,10 @@ import android.content.Context;
import androidx.annotation.NonNull;
import org.nuclearfog.twidda.backend.async.AsyncExecutor.AsyncCallback;
import org.nuclearfog.twidda.backend.async.NotificationLoader;
import org.nuclearfog.twidda.backend.async.NotificationLoader.NotificationLoaderParam;
import org.nuclearfog.twidda.backend.async.NotificationLoader.NotificationLoaderResult;
import org.nuclearfog.twidda.backend.async.PushUpdater;
import org.nuclearfog.twidda.backend.helper.update.PushUpdate;
import org.nuclearfog.twidda.config.GlobalSettings;
@ -14,14 +18,19 @@ import org.unifiedpush.android.connector.MessagingReceiver;
*
* @author nuclearfog
*/
public class PushNotificationReceiver extends MessagingReceiver {
public class PushNotificationReceiver extends MessagingReceiver implements AsyncCallback<NotificationLoaderResult> {
private PushNotification notificationManager;
@Override
public void onMessage(@NonNull Context context, @NonNull byte[] message, @NonNull String instance) {
GlobalSettings settings = GlobalSettings.getInstance(context);
if (settings.pushEnabled()) {
// todo add manual synchonization
NotificationLoader loader = new NotificationLoader(context);
NotificationLoaderParam param = new NotificationLoaderParam(NotificationLoaderParam.LOAD_UNREAD, 0, 0L, 0L);
notificationManager = new PushNotification(context);
loader.execute(param, this);
}
}
@ -29,7 +38,17 @@ public class PushNotificationReceiver extends MessagingReceiver {
@Override
public void onNewEndpoint(@NonNull Context context, @NonNull String endpoint, @NonNull String instance) {
PushUpdater pushUpdater = new PushUpdater(context);
PushUpdate update = new PushUpdate(instance);
PushUpdate update = new PushUpdate(endpoint);
pushUpdater.execute(update, null);
}
@Override
public void onResult(@NonNull NotificationLoaderResult result) {
if (result.notifications != null && !result.notifications.isEmpty()) {
if (notificationManager != null) {
notificationManager.createNotification(result.notifications);
}
}
}
}

View File

@ -372,6 +372,7 @@ public class StatusActivity extends AppCompatActivity implements OnClickListener
notificationLoader.cancel();
translationLoader.cancel();
emojiLoader.cancel();
audioDialog.close();
super.onDestroy();
}

View File

@ -232,6 +232,7 @@ public class StatusEditor extends MediaActivity implements OnClickListener, OnPr
loadingCircle.dismiss();
statusUpdater.cancel();
instanceLoader.cancel();
audioDialog.close();
super.onDestroy();
}

View File

@ -69,8 +69,10 @@ public class VideoViewer extends AppCompatActivity implements Player.Listener {
*/
private static final int CACHE_SIZE = 64000000;
private ExoPlayer player;
private Toolbar toolbar;
private StyledPlayerView playerView;
private ExoPlayer player;
private Uri data;
@ -85,7 +87,7 @@ public class VideoViewer extends AppCompatActivity implements Player.Listener {
protected void onCreate(@Nullable Bundle b) {
super.onCreate(b);
setContentView(R.layout.page_video);
StyledPlayerView playerView = findViewById(R.id.page_video_player);
playerView = findViewById(R.id.page_video_player);
toolbar = findViewById(R.id.page_video_toolbar);
playerView.setShowNextButton(false);
playerView.setShowPreviousButton(false);
@ -157,6 +159,14 @@ public class VideoViewer extends AppCompatActivity implements Player.Listener {
}
@Override
protected void onDestroy() {
// remove player reference to prevent memory leak
playerView.setPlayer(null);
super.onDestroy();
}
@Override
public void onConfigurationChanged(@NonNull Configuration newConfig) {
super.onConfigurationChanged(newConfig);

View File

@ -32,6 +32,8 @@ import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.backend.utils.ConnectionBuilder;
import org.nuclearfog.twidda.backend.utils.LinkUtils;
import java.io.Closeable;
import okhttp3.Call;
/**
@ -39,9 +41,11 @@ import okhttp3.Call;
*
* @author nuclearfog
*/
public class AudioPlayerDialog extends Dialog implements OnClickListener {
public class AudioPlayerDialog extends Dialog implements OnClickListener, Closeable {
private PlayerControlView controls;
private TextView mediaLink;
private ExoPlayer player;
private Uri data;
@ -52,7 +56,7 @@ public class AudioPlayerDialog extends Dialog implements OnClickListener {
public AudioPlayerDialog(@NonNull Context context) {
super(context, R.style.AudioDialog);
setContentView(R.layout.dialog_audio_player);
PlayerControlView controls = findViewById(R.id.dialog_audio_player_controls);
controls = findViewById(R.id.dialog_audio_player_controls);
mediaLink = findViewById(R.id.dialog_audio_player_share);
controls.setShowNextButton(false);
@ -87,6 +91,12 @@ public class AudioPlayerDialog extends Dialog implements OnClickListener {
}
}
@Override
public void close() {
// remove player to prevent memory leak
controls.setPlayer(null);
}
/**
* show dialog and play audio
*

View File

@ -12,6 +12,7 @@ import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.Spinner;
import android.widget.Toast;
import androidx.annotation.NonNull;
@ -34,6 +35,7 @@ import org.nuclearfog.twidda.model.WebPush;
public class WebPushDialog extends Dialog implements OnCheckedChangeListener, OnClickListener, OnItemSelectedListener, AsyncCallback<PushUpdateResult> {
private PushUpdater updater;
private GlobalSettings settings;
private PushUpdate update;
@ -55,7 +57,7 @@ public class WebPushDialog extends Dialog implements OnCheckedChangeListener, On
Button apply_changes = findViewById(R.id.dialog_push_apply);
Spinner policySelector = findViewById(R.id.dialog_push_policy);
GlobalSettings settings = GlobalSettings.getInstance(context);
settings = GlobalSettings.getInstance(context);
updater = new PushUpdater(context);
update = new PushUpdate(settings.getWebPush());
mention.setCheckedImmediately(update.mentionsEnabled());
@ -164,6 +166,8 @@ public class WebPushDialog extends Dialog implements OnCheckedChangeListener, On
@Override
public void onResult(@NonNull PushUpdateResult result) {
if (result.push != null) {
Toast.makeText(getContext(), R.string.info_webpush_update, Toast.LENGTH_SHORT).show();
settings.setWebPush(result.push);
dismiss();
}
}

View File

@ -215,7 +215,7 @@ public class NotificationFragment extends ListFragment implements OnNotification
* @param pos index to insert the new items
*/
private void load(long minId, long maxId, int pos) {
NotificationLoaderParam param = new NotificationLoaderParam(pos, minId, maxId);
NotificationLoaderParam param = new NotificationLoaderParam(NotificationLoaderParam.LOAD_ALL, pos, minId, maxId);
notificationLoader.execute(param, notificationLoaderCallback);
}
}

View File

@ -315,4 +315,9 @@
<string name="dialog_push_title">Pushbenachrichtigungen</string>
<string name="dialog_push_apply">Änderungen anwenden</string>
<string name="settings_enable_push_label">aktiviere Push-Benachrichtigung</string>
<string name="notification_favorite">%1$s hat einen Status favorisiert</string>
<string name="notification_repost">%1$s hat einen Status geteilt</string>
<string name="notification_follow">%1$s folgt dir jetzt</string>
<string name="notification_poll">Eine Umfrage wurde beendet</string>
<string name="notification_mention">%1$s hat dich erwähnt</string>
</resources>

View File

@ -69,6 +69,7 @@
<string name="info_domain_removed">domain removed from the list</string>
<string name="info_hashtag_followed">hashtag followed</string>
<string name="info_domain_blocked">domain blocked!</string>
<string name="info_webpush_update">Push configuration updated!</string>
<string name="info_error">Error</string>
<!-- toast messages for error information -->
@ -342,4 +343,13 @@
<string name="dialog_push_title">Push notifications</string>
<string name="dialog_push_apply">apply changes</string>
<string name="settings_enable_push_label">enable push notification</string>
<string name="notification_favorite">%1$s favorited your status</string>
<string name="notification_repost">%1$s reposted your status</string>
<string name="notification_follow">%1$s followed you</string>
<string name="notification_request">%1$s requested to follow you</string>
<string name="notification_mention">%1$s mentioned you in a status</string>
<string name="notification_status">%1$s has posted a status</string>
<string name="notification_edit">A status you boosted with has been edited</string>
<string name="notification_poll">A poll you have voted in or created has ended</string>
<string name="notification_new">New notifications</string>
</resources>