Rewrite 'service' to Kotlin
This commit is contained in:
@@ -330,7 +330,7 @@ public class MessagesActivity extends AppCompatActivity
|
||||
nManager.cancelAll();
|
||||
|
||||
IntentFilter filter = new IntentFilter();
|
||||
filter.addAction(WebSocketService.NEW_MESSAGE_BROADCAST);
|
||||
filter.addAction(WebSocketService.Companion.getNEW_MESSAGE_BROADCAST());
|
||||
registerReceiver(receiver, filter);
|
||||
new UpdateMissedMessages().execute(viewModel.getMessages().getLastReceivedMessage());
|
||||
|
||||
|
||||
@@ -1,255 +0,0 @@
|
||||
package com.github.gotify.service;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkInfo;
|
||||
import android.os.Build;
|
||||
import android.os.Handler;
|
||||
import com.github.gotify.SSLSettings;
|
||||
import com.github.gotify.Utils;
|
||||
import com.github.gotify.api.Callback;
|
||||
import com.github.gotify.api.CertUtils;
|
||||
import com.github.gotify.client.model.Message;
|
||||
import com.github.gotify.log.Log;
|
||||
import java.util.Calendar;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.WebSocket;
|
||||
import okhttp3.WebSocketListener;
|
||||
|
||||
class WebSocketConnection {
|
||||
private static final AtomicLong ID = new AtomicLong(0);
|
||||
private final ConnectivityManager connectivityManager;
|
||||
private final AlarmManager alarmManager;
|
||||
private OkHttpClient client;
|
||||
|
||||
private final Handler reconnectHandler = new Handler();
|
||||
private Runnable reconnectCallback = this::start;
|
||||
private int errorCount = 0;
|
||||
|
||||
private final String baseUrl;
|
||||
private final String token;
|
||||
private WebSocket webSocket;
|
||||
private Callback.SuccessCallback<Message> onMessage;
|
||||
private Runnable onClose;
|
||||
private Runnable onOpen;
|
||||
private BadRequestRunnable onBadRequest;
|
||||
private OnNetworkFailureRunnable onNetworkFailure;
|
||||
private Runnable onReconnected;
|
||||
private State state;
|
||||
|
||||
WebSocketConnection(
|
||||
String baseUrl,
|
||||
SSLSettings settings,
|
||||
String token,
|
||||
ConnectivityManager connectivityManager,
|
||||
AlarmManager alarmManager) {
|
||||
this.connectivityManager = connectivityManager;
|
||||
this.alarmManager = alarmManager;
|
||||
OkHttpClient.Builder builder =
|
||||
new OkHttpClient.Builder()
|
||||
.readTimeout(0, TimeUnit.MILLISECONDS)
|
||||
.pingInterval(1, TimeUnit.MINUTES)
|
||||
.connectTimeout(10, TimeUnit.SECONDS);
|
||||
CertUtils.applySslSettings(builder, settings);
|
||||
|
||||
client = builder.build();
|
||||
|
||||
this.baseUrl = baseUrl;
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
synchronized WebSocketConnection onMessage(Callback.SuccessCallback<Message> onMessage) {
|
||||
this.onMessage = onMessage;
|
||||
return this;
|
||||
}
|
||||
|
||||
synchronized WebSocketConnection onClose(Runnable onClose) {
|
||||
this.onClose = onClose;
|
||||
return this;
|
||||
}
|
||||
|
||||
synchronized WebSocketConnection onOpen(Runnable onOpen) {
|
||||
this.onOpen = onOpen;
|
||||
return this;
|
||||
}
|
||||
|
||||
synchronized WebSocketConnection onBadRequest(BadRequestRunnable onBadRequest) {
|
||||
this.onBadRequest = onBadRequest;
|
||||
return this;
|
||||
}
|
||||
|
||||
synchronized WebSocketConnection onNetworkFailure(OnNetworkFailureRunnable onNetworkFailure) {
|
||||
this.onNetworkFailure = onNetworkFailure;
|
||||
return this;
|
||||
}
|
||||
|
||||
synchronized WebSocketConnection onReconnected(Runnable onReconnected) {
|
||||
this.onReconnected = onReconnected;
|
||||
return this;
|
||||
}
|
||||
|
||||
private Request request() {
|
||||
HttpUrl url =
|
||||
HttpUrl.parse(baseUrl)
|
||||
.newBuilder()
|
||||
.addPathSegment("stream")
|
||||
.addQueryParameter("token", token)
|
||||
.build();
|
||||
return new Request.Builder().url(url).get().build();
|
||||
}
|
||||
|
||||
public synchronized WebSocketConnection start() {
|
||||
if (state == State.Connecting || state == State.Connected) {
|
||||
return this;
|
||||
}
|
||||
close();
|
||||
state = State.Connecting;
|
||||
long nextId = ID.incrementAndGet();
|
||||
Log.i("WebSocket(" + nextId + "): starting...");
|
||||
|
||||
webSocket = client.newWebSocket(request(), new Listener(nextId));
|
||||
return this;
|
||||
}
|
||||
|
||||
public synchronized void close() {
|
||||
if (webSocket != null) {
|
||||
Log.i("WebSocket(" + ID.get() + "): closing existing connection.");
|
||||
state = State.Disconnected;
|
||||
webSocket.close(1000, "");
|
||||
webSocket = null;
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void scheduleReconnect(long seconds) {
|
||||
if (state == State.Connecting || state == State.Connected) {
|
||||
return;
|
||||
}
|
||||
state = State.Scheduled;
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
Log.i(
|
||||
"WebSocket: scheduling a restart in "
|
||||
+ seconds
|
||||
+ " second(s) (via alarm manager)");
|
||||
final Calendar future = Calendar.getInstance();
|
||||
future.add(Calendar.SECOND, (int) seconds);
|
||||
alarmManager.setExact(
|
||||
AlarmManager.RTC_WAKEUP,
|
||||
future.getTimeInMillis(),
|
||||
"reconnect-tag",
|
||||
this::start,
|
||||
null);
|
||||
} else {
|
||||
Log.i("WebSocket: scheduling a restart in " + seconds + " second(s)");
|
||||
reconnectHandler.removeCallbacks(reconnectCallback);
|
||||
reconnectHandler.postDelayed(reconnectCallback, TimeUnit.SECONDS.toMillis(seconds));
|
||||
}
|
||||
}
|
||||
|
||||
private class Listener extends WebSocketListener {
|
||||
private final long id;
|
||||
|
||||
public Listener(long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(WebSocket webSocket, Response response) {
|
||||
syncExec(
|
||||
() -> {
|
||||
state = State.Connected;
|
||||
Log.i("WebSocket(" + id + "): opened");
|
||||
onOpen.run();
|
||||
|
||||
if (errorCount > 0) {
|
||||
onReconnected.run();
|
||||
errorCount = 0;
|
||||
}
|
||||
});
|
||||
super.onOpen(webSocket, response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessage(WebSocket webSocket, String text) {
|
||||
syncExec(
|
||||
() -> {
|
||||
Log.i("WebSocket(" + id + "): received message " + text);
|
||||
Message message = Utils.JSON.fromJson(text, Message.class);
|
||||
onMessage.onSuccess(message);
|
||||
});
|
||||
super.onMessage(webSocket, text);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClosed(WebSocket webSocket, int code, String reason) {
|
||||
syncExec(
|
||||
() -> {
|
||||
if (state == State.Connected) {
|
||||
Log.w("WebSocket(" + id + "): closed");
|
||||
onClose.run();
|
||||
}
|
||||
state = State.Disconnected;
|
||||
});
|
||||
|
||||
super.onClosed(webSocket, code, reason);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(WebSocket webSocket, Throwable t, Response response) {
|
||||
String code = response != null ? "StatusCode: " + response.code() : "";
|
||||
String message = response != null ? response.message() : "";
|
||||
Log.e("WebSocket(" + id + "): failure " + code + " Message: " + message, t);
|
||||
syncExec(
|
||||
() -> {
|
||||
state = State.Disconnected;
|
||||
if (response != null && response.code() >= 400 && response.code() <= 499) {
|
||||
onBadRequest.execute(message);
|
||||
close();
|
||||
return;
|
||||
}
|
||||
|
||||
errorCount++;
|
||||
|
||||
NetworkInfo network = connectivityManager.getActiveNetworkInfo();
|
||||
if (network == null || !network.isConnected()) {
|
||||
Log.i("WebSocket(" + id + "): Network not connected");
|
||||
}
|
||||
|
||||
int minutes = Math.min(errorCount * 2 - 1, 20);
|
||||
|
||||
onNetworkFailure.execute(minutes);
|
||||
scheduleReconnect(TimeUnit.MINUTES.toSeconds(minutes));
|
||||
});
|
||||
|
||||
super.onFailure(webSocket, t, response);
|
||||
}
|
||||
|
||||
private void syncExec(Runnable runnable) {
|
||||
synchronized (this) {
|
||||
if (ID.get() == id) {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface BadRequestRunnable {
|
||||
void execute(String message);
|
||||
}
|
||||
|
||||
interface OnNetworkFailureRunnable {
|
||||
void execute(int minutes);
|
||||
}
|
||||
|
||||
enum State {
|
||||
Scheduled,
|
||||
Connecting,
|
||||
Connected,
|
||||
Disconnected
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,232 @@
|
||||
package com.github.gotify.service
|
||||
|
||||
import android.app.AlarmManager
|
||||
import android.net.ConnectivityManager
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import com.github.gotify.SSLSettings
|
||||
import com.github.gotify.Utils
|
||||
import com.github.gotify.api.Callback.SuccessCallback
|
||||
import com.github.gotify.api.CertUtils
|
||||
import com.github.gotify.client.model.Message
|
||||
import com.github.gotify.log.Log
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import okhttp3.*
|
||||
|
||||
internal class WebSocketConnection(
|
||||
private val baseUrl: String,
|
||||
settings: SSLSettings?,
|
||||
private val token: String,
|
||||
private val connectivityManager: ConnectivityManager,
|
||||
private val alarmManager: AlarmManager
|
||||
) {
|
||||
companion object {
|
||||
private val ID = AtomicLong(0)
|
||||
}
|
||||
|
||||
private val client: OkHttpClient
|
||||
private val reconnectHandler = Handler(Looper.getMainLooper())
|
||||
private val reconnectCallback = Runnable { start() }
|
||||
private var errorCount = 0
|
||||
|
||||
private var webSocket: WebSocket? = null
|
||||
private var onMessage: SuccessCallback<Message>? = null
|
||||
private var onClose: Runnable? = null
|
||||
private var onOpen: Runnable? = null
|
||||
private var onBadRequest: BadRequestRunnable? = null
|
||||
private var onNetworkFailure: OnNetworkFailureRunnable? = null
|
||||
private var onReconnected: Runnable? = null
|
||||
private var state: State? = null
|
||||
|
||||
init {
|
||||
val builder = OkHttpClient.Builder()
|
||||
.readTimeout(0, TimeUnit.MILLISECONDS)
|
||||
.pingInterval(1, TimeUnit.MINUTES)
|
||||
.connectTimeout(10, TimeUnit.SECONDS)
|
||||
CertUtils.applySslSettings(builder, settings)
|
||||
client = builder.build()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun onMessage(onMessage: SuccessCallback<Message>): WebSocketConnection {
|
||||
this.onMessage = onMessage
|
||||
return this
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun onClose(onClose: Runnable): WebSocketConnection {
|
||||
this.onClose = onClose
|
||||
return this
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun onOpen(onOpen: Runnable): WebSocketConnection {
|
||||
this.onOpen = onOpen
|
||||
return this
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun onBadRequest(onBadRequest: BadRequestRunnable): WebSocketConnection {
|
||||
this.onBadRequest = onBadRequest
|
||||
return this
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun onNetworkFailure(onNetworkFailure: OnNetworkFailureRunnable): WebSocketConnection {
|
||||
this.onNetworkFailure = onNetworkFailure
|
||||
return this
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun onReconnected(onReconnected: Runnable): WebSocketConnection {
|
||||
this.onReconnected = onReconnected
|
||||
return this
|
||||
}
|
||||
|
||||
private fun request(): Request {
|
||||
val url = HttpUrl.parse(baseUrl)!!
|
||||
.newBuilder()
|
||||
.addPathSegment("stream")
|
||||
.addQueryParameter("token", token)
|
||||
.build()
|
||||
return Request.Builder().url(url).get().build()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun start(): WebSocketConnection {
|
||||
if (state == State.Connecting || state == State.Connected) {
|
||||
return this
|
||||
}
|
||||
close()
|
||||
state = State.Connecting
|
||||
val nextId = ID.incrementAndGet()
|
||||
Log.i("WebSocket($nextId): starting...")
|
||||
|
||||
webSocket = client.newWebSocket(request(), Listener(nextId))
|
||||
return this
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun close() {
|
||||
if (webSocket != null) {
|
||||
Log.i("WebSocket(${ID.get()}): closing existing connection.")
|
||||
state = State.Disconnected
|
||||
webSocket!!.close(1000, "")
|
||||
webSocket = null
|
||||
}
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun scheduleReconnect(seconds: Long) {
|
||||
if (state == State.Connecting || state == State.Connected) {
|
||||
return
|
||||
}
|
||||
state = State.Scheduled
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
Log.i("WebSocket: scheduling a restart in $seconds second(s) (via alarm manager)")
|
||||
val future = Calendar.getInstance()
|
||||
future.add(Calendar.SECOND, seconds.toInt())
|
||||
alarmManager.setExact(
|
||||
AlarmManager.RTC_WAKEUP,
|
||||
future.timeInMillis,
|
||||
"reconnect-tag",
|
||||
{ start() },
|
||||
null
|
||||
)
|
||||
} else {
|
||||
Log.i("WebSocket: scheduling a restart in $seconds second(s)")
|
||||
reconnectHandler.removeCallbacks(reconnectCallback)
|
||||
reconnectHandler.postDelayed(reconnectCallback, TimeUnit.SECONDS.toMillis(seconds))
|
||||
}
|
||||
}
|
||||
|
||||
private inner class Listener(private val id: Long) : WebSocketListener() {
|
||||
override fun onOpen(webSocket: WebSocket, response: Response) {
|
||||
syncExec {
|
||||
state = State.Connected
|
||||
Log.i("WebSocket($id): opened")
|
||||
onOpen!!.run()
|
||||
|
||||
if (errorCount > 0) {
|
||||
onReconnected!!.run()
|
||||
errorCount = 0
|
||||
}
|
||||
}
|
||||
super.onOpen(webSocket, response)
|
||||
}
|
||||
|
||||
override fun onMessage(webSocket: WebSocket, text: String) {
|
||||
syncExec {
|
||||
Log.i("WebSocket($id): received message $text")
|
||||
val message = Utils.JSON.fromJson(text, Message::class.java)
|
||||
onMessage!!.onSuccess(message)
|
||||
}
|
||||
super.onMessage(webSocket, text)
|
||||
}
|
||||
|
||||
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
|
||||
syncExec {
|
||||
if (state == State.Connected) {
|
||||
Log.w("WebSocket($id): closed")
|
||||
onClose!!.run()
|
||||
}
|
||||
state = State.Disconnected
|
||||
}
|
||||
super.onClosed(webSocket, code, reason)
|
||||
}
|
||||
|
||||
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
|
||||
val code = if (response != null) "StatusCode: ${response.code()}" else ""
|
||||
val message = if (response != null) response.message() else ""
|
||||
Log.e("WebSocket($id): failure $code Message: $message", t)
|
||||
syncExec {
|
||||
state = State.Disconnected
|
||||
if (response != null && response.code() >= 400 && response.code() <= 499) {
|
||||
onBadRequest!!.execute(message)
|
||||
close()
|
||||
return@syncExec
|
||||
}
|
||||
|
||||
errorCount++
|
||||
|
||||
val network = connectivityManager.activeNetworkInfo
|
||||
if (network == null || !network.isConnected) {
|
||||
Log.i("WebSocket($id): Network not connected")
|
||||
}
|
||||
|
||||
val minutes = (errorCount * 2 - 1).coerceAtMost(20)
|
||||
|
||||
onNetworkFailure!!.execute(minutes)
|
||||
scheduleReconnect(TimeUnit.MINUTES.toSeconds(minutes.toLong()))
|
||||
}
|
||||
super.onFailure(webSocket, t, response)
|
||||
}
|
||||
|
||||
private fun syncExec(runnable: Runnable) {
|
||||
synchronized(this) {
|
||||
if (ID.get() == id) {
|
||||
runnable.run()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal interface BadRequestRunnable {
|
||||
fun execute(message: String)
|
||||
}
|
||||
|
||||
internal interface OnNetworkFailureRunnable {
|
||||
fun execute(minutes: Int)
|
||||
}
|
||||
|
||||
internal enum class State {
|
||||
Scheduled,
|
||||
Connecting,
|
||||
Connected,
|
||||
Disconnected
|
||||
}
|
||||
}
|
||||
@@ -1,389 +0,0 @@
|
||||
package com.github.gotify.service;
|
||||
|
||||
import android.app.AlarmManager;
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.graphics.Color;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import com.github.gotify.MarkwonFactory;
|
||||
import com.github.gotify.MissedMessageUtil;
|
||||
import com.github.gotify.NotificationSupport;
|
||||
import com.github.gotify.R;
|
||||
import com.github.gotify.Settings;
|
||||
import com.github.gotify.Utils;
|
||||
import com.github.gotify.api.ClientFactory;
|
||||
import com.github.gotify.client.ApiClient;
|
||||
import com.github.gotify.client.api.MessageApi;
|
||||
import com.github.gotify.client.model.Message;
|
||||
import com.github.gotify.log.Log;
|
||||
import com.github.gotify.log.UncaughtExceptionHandler;
|
||||
import com.github.gotify.messages.Extras;
|
||||
import com.github.gotify.messages.MessagesActivity;
|
||||
import com.github.gotify.picasso.PicassoHandler;
|
||||
import io.noties.markwon.Markwon;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static com.github.gotify.api.Callback.call;
|
||||
|
||||
public class WebSocketService extends Service {
|
||||
|
||||
public static final String NEW_MESSAGE_BROADCAST =
|
||||
WebSocketService.class.getName() + ".NEW_MESSAGE";
|
||||
|
||||
private static final long NOT_LOADED = -2;
|
||||
|
||||
private Settings settings;
|
||||
private WebSocketConnection connection;
|
||||
|
||||
private AtomicLong lastReceivedMessage = new AtomicLong(NOT_LOADED);
|
||||
private MissedMessageUtil missingMessageUtil;
|
||||
|
||||
private PicassoHandler picassoHandler;
|
||||
private Markwon markwon;
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
settings = new Settings(this);
|
||||
ApiClient client =
|
||||
ClientFactory.clientToken(settings.url(), settings.sslSettings(), settings.token());
|
||||
missingMessageUtil = new MissedMessageUtil(client.createService(MessageApi.class));
|
||||
Log.i("Create " + getClass().getSimpleName());
|
||||
picassoHandler = new PicassoHandler(this, settings);
|
||||
markwon = MarkwonFactory.createForNotification(this, picassoHandler.get());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
if (connection != null) {
|
||||
connection.close();
|
||||
}
|
||||
Log.w("Destroy " + getClass().getSimpleName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
Log.init(this);
|
||||
|
||||
if (connection != null) {
|
||||
connection.close();
|
||||
}
|
||||
|
||||
Log.i("Starting " + getClass().getSimpleName());
|
||||
super.onStartCommand(intent, flags, startId);
|
||||
new Thread(this::startPushService).run();
|
||||
|
||||
return START_STICKY;
|
||||
}
|
||||
|
||||
private void startPushService() {
|
||||
UncaughtExceptionHandler.registerCurrentThread();
|
||||
showForegroundNotification(getString(R.string.websocket_init));
|
||||
|
||||
if (lastReceivedMessage.get() == NOT_LOADED) {
|
||||
missingMessageUtil.lastReceivedMessage(lastReceivedMessage::set);
|
||||
}
|
||||
|
||||
ConnectivityManager cm =
|
||||
(ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
|
||||
|
||||
connection =
|
||||
new WebSocketConnection(
|
||||
settings.url(),
|
||||
settings.sslSettings(),
|
||||
settings.token(),
|
||||
cm,
|
||||
alarmManager)
|
||||
.onOpen(this::onOpen)
|
||||
.onClose(this::onClose)
|
||||
.onBadRequest(this::onBadRequest)
|
||||
.onNetworkFailure(this::onNetworkFailure)
|
||||
.onMessage(this::onMessage)
|
||||
.onReconnected(this::notifyMissedNotifications)
|
||||
.start();
|
||||
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
|
||||
|
||||
picassoHandler.updateAppIds();
|
||||
}
|
||||
|
||||
private void onClose() {
|
||||
showForegroundNotification(
|
||||
getString(R.string.websocket_closed), getString(R.string.websocket_reconnect));
|
||||
ClientFactory.userApiWithToken(settings)
|
||||
.currentUser()
|
||||
.enqueue(
|
||||
call(
|
||||
(ignored) -> this.doReconnect(),
|
||||
(exception) -> {
|
||||
if (exception.code() == 401) {
|
||||
showForegroundNotification(
|
||||
getString(R.string.user_action),
|
||||
getString(R.string.websocket_closed_logout));
|
||||
} else {
|
||||
Log.i(
|
||||
"WebSocket closed but the user still authenticated, trying to reconnect");
|
||||
this.doReconnect();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void doReconnect() {
|
||||
if (connection == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
connection.scheduleReconnect(15);
|
||||
}
|
||||
|
||||
private void onBadRequest(String message) {
|
||||
showForegroundNotification(getString(R.string.websocket_could_not_connect), message);
|
||||
}
|
||||
|
||||
private void onNetworkFailure(int minutes) {
|
||||
String status = getString(R.string.websocket_not_connected);
|
||||
String intervalUnit =
|
||||
getResources()
|
||||
.getQuantityString(R.plurals.websocket_retry_interval, minutes, minutes);
|
||||
showForegroundNotification(
|
||||
status, getString(R.string.websocket_reconnect) + ' ' + intervalUnit);
|
||||
}
|
||||
|
||||
private void onOpen() {
|
||||
showForegroundNotification(getString(R.string.websocket_listening));
|
||||
}
|
||||
|
||||
private void notifyMissedNotifications() {
|
||||
long messageId = lastReceivedMessage.get();
|
||||
if (messageId == NOT_LOADED) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<Message> messages = missingMessageUtil.missingMessages(messageId);
|
||||
|
||||
if (messages.size() > 5) {
|
||||
onGroupedMessages(messages);
|
||||
} else {
|
||||
for (Message message : messages) {
|
||||
onMessage(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onGroupedMessages(List<Message> messages) {
|
||||
long highestPriority = 0;
|
||||
for (Message message : messages) {
|
||||
if (lastReceivedMessage.get() < message.getId()) {
|
||||
lastReceivedMessage.set(message.getId());
|
||||
highestPriority = Math.max(highestPriority, message.getPriority());
|
||||
}
|
||||
broadcast(message);
|
||||
}
|
||||
int size = messages.size();
|
||||
showNotification(
|
||||
NotificationSupport.ID.GROUPED,
|
||||
getString(R.string.missed_messages),
|
||||
getString(R.string.grouped_message, size),
|
||||
highestPriority,
|
||||
null);
|
||||
}
|
||||
|
||||
private void onMessage(Message message) {
|
||||
if (lastReceivedMessage.get() < message.getId()) {
|
||||
lastReceivedMessage.set(message.getId());
|
||||
}
|
||||
|
||||
broadcast(message);
|
||||
showNotification(
|
||||
message.getId(),
|
||||
message.getTitle(),
|
||||
message.getMessage(),
|
||||
message.getPriority(),
|
||||
message.getExtras(),
|
||||
message.getAppid());
|
||||
}
|
||||
|
||||
private void broadcast(Message message) {
|
||||
Intent intent = new Intent();
|
||||
intent.setAction(NEW_MESSAGE_BROADCAST);
|
||||
intent.putExtra("message", Utils.JSON.toJson(message));
|
||||
sendBroadcast(intent);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
private void showForegroundNotification(String title) {
|
||||
showForegroundNotification(title, null);
|
||||
}
|
||||
|
||||
private void showForegroundNotification(String title, String message) {
|
||||
Intent notificationIntent = new Intent(this, MessagesActivity.class);
|
||||
|
||||
PendingIntent pendingIntent =
|
||||
PendingIntent.getActivity(
|
||||
this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
NotificationCompat.Builder notificationBuilder =
|
||||
new NotificationCompat.Builder(this, NotificationSupport.Channel.FOREGROUND);
|
||||
notificationBuilder.setSmallIcon(R.drawable.ic_gotify);
|
||||
notificationBuilder.setOngoing(true);
|
||||
notificationBuilder.setPriority(NotificationCompat.PRIORITY_MIN);
|
||||
notificationBuilder.setShowWhen(false);
|
||||
notificationBuilder.setWhen(0);
|
||||
notificationBuilder.setContentTitle(title);
|
||||
|
||||
if (message != null) {
|
||||
notificationBuilder.setContentText(message);
|
||||
notificationBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText(message));
|
||||
}
|
||||
|
||||
notificationBuilder.setContentIntent(pendingIntent);
|
||||
notificationBuilder.setColor(
|
||||
ContextCompat.getColor(getApplicationContext(), R.color.colorPrimary));
|
||||
|
||||
startForeground(NotificationSupport.ID.FOREGROUND, notificationBuilder.build());
|
||||
}
|
||||
|
||||
private void showNotification(
|
||||
int id, String title, String message, long priority, Map<String, Object> extras) {
|
||||
showNotification(id, title, message, priority, extras, -1L);
|
||||
}
|
||||
|
||||
private void showNotification(
|
||||
long id,
|
||||
String title,
|
||||
String message,
|
||||
long priority,
|
||||
Map<String, Object> extras,
|
||||
Long appid) {
|
||||
|
||||
Intent intent;
|
||||
|
||||
String intentUrl =
|
||||
Extras.getNestedValue(
|
||||
String.class, extras, "android::action", "onReceive", "intentUrl");
|
||||
|
||||
if (intentUrl != null) {
|
||||
intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(intentUrl));
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
String url =
|
||||
Extras.getNestedValue(String.class, extras, "client::notification", "click", "url");
|
||||
|
||||
if (url != null) {
|
||||
intent = new Intent(Intent.ACTION_VIEW);
|
||||
intent.setData(Uri.parse(url));
|
||||
} else {
|
||||
intent = new Intent(this, MessagesActivity.class);
|
||||
}
|
||||
|
||||
PendingIntent contentIntent =
|
||||
PendingIntent.getActivity(
|
||||
this,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
NotificationCompat.Builder b =
|
||||
new NotificationCompat.Builder(
|
||||
this, NotificationSupport.convertPriorityToChannel(priority));
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
showNotificationGroup(priority);
|
||||
}
|
||||
|
||||
b.setAutoCancel(true)
|
||||
.setDefaults(Notification.DEFAULT_ALL)
|
||||
.setWhen(System.currentTimeMillis())
|
||||
.setSmallIcon(R.drawable.ic_gotify)
|
||||
.setLargeIcon(picassoHandler.getIcon(appid))
|
||||
.setTicker(getString(R.string.app_name) + " - " + title)
|
||||
.setGroup(NotificationSupport.Group.MESSAGES)
|
||||
.setContentTitle(title)
|
||||
.setDefaults(Notification.DEFAULT_LIGHTS | Notification.DEFAULT_SOUND)
|
||||
.setLights(Color.CYAN, 1000, 5000)
|
||||
.setColor(ContextCompat.getColor(getApplicationContext(), R.color.colorPrimary))
|
||||
.setContentIntent(contentIntent);
|
||||
|
||||
CharSequence formattedMessage = message;
|
||||
if (Extras.useMarkdown(extras)) {
|
||||
formattedMessage = markwon.toMarkdown(message);
|
||||
message = formattedMessage.toString();
|
||||
}
|
||||
b.setContentText(message);
|
||||
b.setStyle(new NotificationCompat.BigTextStyle().bigText(formattedMessage));
|
||||
|
||||
String notificationImageUrl =
|
||||
Extras.getNestedValue(String.class, extras, "client::notification", "bigImageUrl");
|
||||
|
||||
if (notificationImageUrl != null) {
|
||||
try {
|
||||
b.setStyle(
|
||||
new NotificationCompat.BigPictureStyle()
|
||||
.bigPicture(picassoHandler.getImageFromUrl(notificationImageUrl)));
|
||||
} catch (Exception e) {
|
||||
Log.e("Error loading bigImageUrl", e);
|
||||
}
|
||||
}
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(Utils.longToInt(id), b.build());
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
public void showNotificationGroup(long priority) {
|
||||
Intent intent = new Intent(this, MessagesActivity.class);
|
||||
PendingIntent contentIntent =
|
||||
PendingIntent.getActivity(
|
||||
this,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
|
||||
|
||||
NotificationCompat.Builder b =
|
||||
new NotificationCompat.Builder(
|
||||
this, NotificationSupport.convertPriorityToChannel(priority));
|
||||
|
||||
b.setAutoCancel(true)
|
||||
.setDefaults(Notification.DEFAULT_ALL)
|
||||
.setWhen(System.currentTimeMillis())
|
||||
.setSmallIcon(R.drawable.ic_gotify)
|
||||
.setTicker(getString(R.string.app_name))
|
||||
.setGroup(NotificationSupport.Group.MESSAGES)
|
||||
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
|
||||
.setContentTitle(getString(R.string.grouped_notification_text))
|
||||
.setGroupSummary(true)
|
||||
.setContentText(getString(R.string.grouped_notification_text))
|
||||
.setColor(ContextCompat.getColor(getApplicationContext(), R.color.colorPrimary))
|
||||
.setContentIntent(contentIntent);
|
||||
|
||||
NotificationManager notificationManager =
|
||||
(NotificationManager) this.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
notificationManager.notify(-5, b.build());
|
||||
}
|
||||
}
|
||||
384
app/src/main/java/com/github/gotify/service/WebSocketService.kt
Normal file
384
app/src/main/java/com/github/gotify/service/WebSocketService.kt
Normal file
@@ -0,0 +1,384 @@
|
||||
package com.github.gotify.service
|
||||
|
||||
import android.app.*
|
||||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.graphics.Color
|
||||
import android.net.ConnectivityManager
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.IBinder
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.github.gotify.*
|
||||
import com.github.gotify.api.Callback
|
||||
import com.github.gotify.api.ClientFactory
|
||||
import com.github.gotify.client.api.MessageApi
|
||||
import com.github.gotify.client.model.Message
|
||||
import com.github.gotify.log.Log
|
||||
import com.github.gotify.log.UncaughtExceptionHandler
|
||||
import com.github.gotify.messages.Extras
|
||||
import com.github.gotify.messages.MessagesActivity
|
||||
import com.github.gotify.picasso.PicassoHandler
|
||||
import com.github.gotify.service.WebSocketConnection.BadRequestRunnable
|
||||
import com.github.gotify.service.WebSocketConnection.OnNetworkFailureRunnable
|
||||
import io.noties.markwon.Markwon
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
|
||||
class WebSocketService : Service() {
|
||||
companion object {
|
||||
val NEW_MESSAGE_BROADCAST = "${WebSocketService::class.java.name}.NEW_MESSAGE"
|
||||
private const val NOT_LOADED = -2L
|
||||
}
|
||||
|
||||
private lateinit var settings: Settings
|
||||
private var connection: WebSocketConnection? = null
|
||||
|
||||
private val lastReceivedMessage = AtomicLong(NOT_LOADED)
|
||||
private lateinit var missingMessageUtil: MissedMessageUtil
|
||||
|
||||
private lateinit var picassoHandler: PicassoHandler
|
||||
private lateinit var markwon: Markwon
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
settings = Settings(this)
|
||||
val client = ClientFactory.clientToken(
|
||||
settings.url(),
|
||||
settings.sslSettings(),
|
||||
settings.token()
|
||||
)
|
||||
missingMessageUtil = MissedMessageUtil(client.createService(MessageApi::class.java))
|
||||
Log.i("Create ${javaClass.simpleName}")
|
||||
picassoHandler = PicassoHandler(this, settings)
|
||||
markwon = MarkwonFactory.createForNotification(this, picassoHandler.get())
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (connection != null) {
|
||||
connection!!.close()
|
||||
}
|
||||
Log.w("Destroy ${javaClass.simpleName}")
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
|
||||
Log.init(this)
|
||||
if (connection != null) {
|
||||
connection!!.close()
|
||||
}
|
||||
Log.i("Starting ${javaClass.simpleName}")
|
||||
super.onStartCommand(intent, flags, startId)
|
||||
Thread { startPushService() }.start()
|
||||
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
private fun startPushService() {
|
||||
UncaughtExceptionHandler.registerCurrentThread()
|
||||
showForegroundNotification(getString(R.string.websocket_init))
|
||||
|
||||
if (lastReceivedMessage.get() == NOT_LOADED) {
|
||||
missingMessageUtil.lastReceivedMessage { lastReceivedMessage.set(it) }
|
||||
}
|
||||
|
||||
val cm = getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
val alarmManager = getSystemService(ALARM_SERVICE) as AlarmManager
|
||||
|
||||
connection = WebSocketConnection(
|
||||
settings.url(),
|
||||
settings.sslSettings(),
|
||||
settings.token(),
|
||||
cm,
|
||||
alarmManager
|
||||
)
|
||||
.onOpen { onOpen() }
|
||||
.onClose { onClose() }
|
||||
.onBadRequest(object : BadRequestRunnable {
|
||||
override fun execute(message: String) {
|
||||
onBadRequest(message)
|
||||
}
|
||||
})
|
||||
.onNetworkFailure(object : OnNetworkFailureRunnable {
|
||||
override fun execute(minutes: Int) {
|
||||
onNetworkFailure(minutes)
|
||||
}
|
||||
})
|
||||
.onMessage { onMessage(it) }
|
||||
.onReconnected { notifyMissedNotifications() }
|
||||
.start()
|
||||
|
||||
val intentFilter = IntentFilter()
|
||||
intentFilter.addAction(ConnectivityManager.CONNECTIVITY_ACTION)
|
||||
|
||||
picassoHandler.updateAppIds()
|
||||
}
|
||||
|
||||
private fun onClose() {
|
||||
showForegroundNotification(
|
||||
getString(R.string.websocket_closed), getString(R.string.websocket_reconnect)
|
||||
)
|
||||
ClientFactory.userApiWithToken(settings)
|
||||
.currentUser()
|
||||
.enqueue(Callback.call({ doReconnect() }) { exception ->
|
||||
if (exception.code() == 401) {
|
||||
showForegroundNotification(
|
||||
getString(R.string.user_action),
|
||||
getString(R.string.websocket_closed_logout)
|
||||
)
|
||||
} else {
|
||||
Log.i("WebSocket closed but the user still authenticated, trying to reconnect")
|
||||
doReconnect()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun doReconnect() {
|
||||
if (connection == null) {
|
||||
return
|
||||
}
|
||||
connection!!.scheduleReconnect(15)
|
||||
}
|
||||
|
||||
private fun onBadRequest(message: String) {
|
||||
showForegroundNotification(getString(R.string.websocket_could_not_connect), message)
|
||||
}
|
||||
|
||||
private fun onNetworkFailure(minutes: Int) {
|
||||
val status = getString(R.string.websocket_not_connected)
|
||||
val intervalUnit = resources
|
||||
.getQuantityString(R.plurals.websocket_retry_interval, minutes, minutes)
|
||||
showForegroundNotification(
|
||||
status, "${getString(R.string.websocket_reconnect)} $intervalUnit"
|
||||
)
|
||||
}
|
||||
|
||||
private fun onOpen() {
|
||||
showForegroundNotification(getString(R.string.websocket_listening))
|
||||
}
|
||||
|
||||
private fun notifyMissedNotifications() {
|
||||
val messageId = lastReceivedMessage.get()
|
||||
if (messageId == NOT_LOADED) {
|
||||
return
|
||||
}
|
||||
|
||||
val messages = missingMessageUtil.missingMessages(messageId)
|
||||
|
||||
if (messages.size > 5) {
|
||||
onGroupedMessages(messages)
|
||||
} else {
|
||||
for (message in messages) {
|
||||
onMessage(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onGroupedMessages(messages: List<Message>) {
|
||||
var highestPriority = 0L
|
||||
for (message in messages) {
|
||||
if (lastReceivedMessage.get() < message.id) {
|
||||
lastReceivedMessage.set(message.id)
|
||||
highestPriority = highestPriority.coerceAtLeast(message.priority)
|
||||
}
|
||||
broadcast(message)
|
||||
}
|
||||
val size = messages.size
|
||||
showNotification(
|
||||
NotificationSupport.ID.GROUPED,
|
||||
getString(R.string.missed_messages),
|
||||
getString(R.string.grouped_message, size),
|
||||
highestPriority,
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
private fun onMessage(message: Message) {
|
||||
if (lastReceivedMessage.get() < message.id) {
|
||||
lastReceivedMessage.set(message.id)
|
||||
}
|
||||
broadcast(message)
|
||||
showNotification(
|
||||
message.id,
|
||||
message.title,
|
||||
message.message,
|
||||
message.priority,
|
||||
message.extras,
|
||||
message.appid
|
||||
)
|
||||
}
|
||||
|
||||
private fun broadcast(message: Message) {
|
||||
val intent = Intent()
|
||||
intent.action = NEW_MESSAGE_BROADCAST
|
||||
intent.putExtra("message", Utils.JSON.toJson(message))
|
||||
sendBroadcast(intent)
|
||||
}
|
||||
|
||||
override fun onBind(intent: Intent): IBinder? = null
|
||||
|
||||
private fun showForegroundNotification(title: String, message: String? = null) {
|
||||
val notificationIntent = Intent(this, MessagesActivity::class.java)
|
||||
|
||||
val pendingIntent = PendingIntent.getActivity(
|
||||
this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
val notificationBuilder =
|
||||
NotificationCompat.Builder(this, NotificationSupport.Channel.FOREGROUND)
|
||||
notificationBuilder.setSmallIcon(R.drawable.ic_gotify)
|
||||
notificationBuilder.setOngoing(true)
|
||||
notificationBuilder.priority = NotificationCompat.PRIORITY_MIN
|
||||
notificationBuilder.setShowWhen(false)
|
||||
notificationBuilder.setWhen(0)
|
||||
notificationBuilder.setContentTitle(title)
|
||||
|
||||
if (message != null) {
|
||||
notificationBuilder.setContentText(message)
|
||||
notificationBuilder.setStyle(NotificationCompat.BigTextStyle().bigText(message))
|
||||
}
|
||||
|
||||
notificationBuilder.setContentIntent(pendingIntent)
|
||||
notificationBuilder.color = ContextCompat.getColor(applicationContext, R.color.colorPrimary)
|
||||
startForeground(NotificationSupport.ID.FOREGROUND, notificationBuilder.build())
|
||||
}
|
||||
|
||||
private fun showNotification(
|
||||
id: Int,
|
||||
title: String,
|
||||
message: String,
|
||||
priority: Long,
|
||||
extras: Map<String, Any>?
|
||||
) {
|
||||
showNotification(id.toLong(), title, message, priority, extras, -1L)
|
||||
}
|
||||
|
||||
private fun showNotification(
|
||||
id: Long,
|
||||
title: String,
|
||||
message: String,
|
||||
priority: Long,
|
||||
extras: Map<String, Any>?,
|
||||
appId: Long
|
||||
) {
|
||||
var intent: Intent
|
||||
|
||||
val intentUrl = Extras.getNestedValue(
|
||||
String::class.java,
|
||||
extras,
|
||||
"android::action",
|
||||
"onReceive",
|
||||
"intentUrl"
|
||||
)
|
||||
|
||||
if (intentUrl != null) {
|
||||
intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = Uri.parse(intentUrl)
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
val url = Extras.getNestedValue(
|
||||
String::class.java,
|
||||
extras,
|
||||
"client::notification",
|
||||
"click",
|
||||
"url"
|
||||
)
|
||||
|
||||
if (url != null) {
|
||||
intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = Uri.parse(url)
|
||||
} else {
|
||||
intent = Intent(this, MessagesActivity::class.java)
|
||||
}
|
||||
|
||||
val contentIntent = PendingIntent.getActivity(
|
||||
this,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
val b = NotificationCompat.Builder(
|
||||
this, NotificationSupport.convertPriorityToChannel(priority)
|
||||
)
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
showNotificationGroup(priority)
|
||||
}
|
||||
|
||||
b.setAutoCancel(true)
|
||||
.setDefaults(Notification.DEFAULT_ALL)
|
||||
.setWhen(System.currentTimeMillis())
|
||||
.setSmallIcon(R.drawable.ic_gotify)
|
||||
.setLargeIcon(picassoHandler.getIcon(appId))
|
||||
.setTicker("${getString(R.string.app_name)} - $title")
|
||||
.setGroup(NotificationSupport.Group.MESSAGES)
|
||||
.setContentTitle(title)
|
||||
.setDefaults(Notification.DEFAULT_LIGHTS or Notification.DEFAULT_SOUND)
|
||||
.setLights(Color.CYAN, 1000, 5000)
|
||||
.setColor(ContextCompat.getColor(applicationContext, R.color.colorPrimary))
|
||||
.setContentIntent(contentIntent)
|
||||
|
||||
var formattedMessage = message as CharSequence
|
||||
lateinit var newMessage: String
|
||||
if (Extras.useMarkdown(extras)) {
|
||||
formattedMessage = markwon.toMarkdown(message)
|
||||
newMessage = formattedMessage.toString()
|
||||
}
|
||||
b.setContentText(newMessage)
|
||||
b.setStyle(NotificationCompat.BigTextStyle().bigText(formattedMessage))
|
||||
|
||||
val notificationImageUrl = Extras.getNestedValue(
|
||||
String::class.java,
|
||||
extras,
|
||||
"client::notification",
|
||||
"bigImageUrl"
|
||||
)
|
||||
|
||||
if (notificationImageUrl != null) {
|
||||
try {
|
||||
b.setStyle(
|
||||
NotificationCompat.BigPictureStyle()
|
||||
.bigPicture(picassoHandler.getImageFromUrl(notificationImageUrl))
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
Log.e("Error loading bigImageUrl", e)
|
||||
}
|
||||
}
|
||||
val notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.notify(Utils.longToInt(id), b.build())
|
||||
}
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
fun showNotificationGroup(priority: Long) {
|
||||
val intent = Intent(this, MessagesActivity::class.java)
|
||||
val contentIntent = PendingIntent.getActivity(
|
||||
this,
|
||||
0,
|
||||
intent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
val builder = NotificationCompat.Builder(
|
||||
this, NotificationSupport.convertPriorityToChannel(priority)
|
||||
)
|
||||
|
||||
builder.setAutoCancel(true)
|
||||
.setDefaults(Notification.DEFAULT_ALL)
|
||||
.setWhen(System.currentTimeMillis())
|
||||
.setSmallIcon(R.drawable.ic_gotify)
|
||||
.setTicker(getString(R.string.app_name))
|
||||
.setGroup(NotificationSupport.Group.MESSAGES)
|
||||
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN)
|
||||
.setContentTitle(getString(R.string.grouped_notification_text))
|
||||
.setGroupSummary(true)
|
||||
.setContentText(getString(R.string.grouped_notification_text))
|
||||
.setColor(ContextCompat.getColor(applicationContext, R.color.colorPrimary))
|
||||
.setContentIntent(contentIntent)
|
||||
|
||||
val notificationManager = this.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
|
||||
notificationManager.notify(-5, builder.build())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user