2018-02-02 00:01:24 +00:00
|
|
|
package org.thoughtcrime.securesms.service;
|
|
|
|
|
|
|
|
import android.app.PendingIntent;
|
|
|
|
import android.app.Service;
|
|
|
|
import android.content.Context;
|
|
|
|
import android.content.Intent;
|
2019-06-27 16:18:52 +00:00
|
|
|
import android.os.Binder;
|
2018-02-02 00:01:24 +00:00
|
|
|
import android.os.IBinder;
|
2019-06-27 16:18:52 +00:00
|
|
|
|
2019-06-05 19:47:14 +00:00
|
|
|
import androidx.annotation.DrawableRes;
|
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
import androidx.annotation.Nullable;
|
|
|
|
import androidx.core.app.NotificationCompat;
|
|
|
|
import androidx.core.content.ContextCompat;
|
2018-02-02 00:01:24 +00:00
|
|
|
|
2020-12-04 23:31:58 +00:00
|
|
|
import org.signal.core.util.logging.Log;
|
2019-11-14 19:35:08 +00:00
|
|
|
import org.thoughtcrime.securesms.MainActivity;
|
2018-02-02 00:01:24 +00:00
|
|
|
import org.thoughtcrime.securesms.R;
|
2018-08-06 16:20:24 +00:00
|
|
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
2018-02-02 00:01:24 +00:00
|
|
|
|
2019-06-27 16:18:52 +00:00
|
|
|
import java.util.Iterator;
|
|
|
|
import java.util.LinkedHashMap;
|
|
|
|
import java.util.Locale;
|
|
|
|
import java.util.Objects;
|
|
|
|
import java.util.concurrent.atomic.AtomicInteger;
|
|
|
|
|
|
|
|
public final class GenericForegroundService extends Service {
|
|
|
|
|
|
|
|
private static final String TAG = Log.tag(GenericForegroundService.class);
|
2018-02-02 00:01:24 +00:00
|
|
|
|
2019-06-27 16:18:52 +00:00
|
|
|
private final IBinder binder = new LocalBinder();
|
2018-10-09 00:44:23 +00:00
|
|
|
|
2019-06-27 16:18:52 +00:00
|
|
|
private static final int NOTIFICATION_ID = 827353982;
|
|
|
|
private static final String EXTRA_TITLE = "extra_title";
|
|
|
|
private static final String EXTRA_CHANNEL_ID = "extra_channel_id";
|
|
|
|
private static final String EXTRA_ICON_RES = "extra_icon_res";
|
|
|
|
private static final String EXTRA_ID = "extra_id";
|
|
|
|
private static final String EXTRA_PROGRESS = "extra_progress";
|
|
|
|
private static final String EXTRA_PROGRESS_MAX = "extra_progress_max";
|
|
|
|
private static final String EXTRA_PROGRESS_INDETERMINATE = "extra_progress_indeterminate";
|
2018-02-02 00:01:24 +00:00
|
|
|
|
|
|
|
private static final String ACTION_START = "start";
|
|
|
|
private static final String ACTION_STOP = "stop";
|
|
|
|
|
2019-06-27 16:18:52 +00:00
|
|
|
private static final AtomicInteger NEXT_ID = new AtomicInteger();
|
2018-02-02 00:01:24 +00:00
|
|
|
|
2019-06-27 16:18:52 +00:00
|
|
|
private final LinkedHashMap<Integer, Entry> allActiveMessages = new LinkedHashMap<>();
|
2018-02-02 00:01:24 +00:00
|
|
|
|
2020-10-15 16:55:08 +00:00
|
|
|
private static final Entry DEFAULTS = new Entry("", NotificationChannels.OTHER, R.drawable.ic_notification, -1, 0, 0, false);
|
2019-06-27 16:18:52 +00:00
|
|
|
|
|
|
|
private @Nullable Entry lastPosted;
|
2018-02-02 00:01:24 +00:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public int onStartCommand(Intent intent, int flags, int startId) {
|
2019-06-27 16:18:52 +00:00
|
|
|
if (intent == null) {
|
|
|
|
throw new IllegalStateException("Intent needs to be non-null.");
|
|
|
|
}
|
|
|
|
|
2018-10-09 00:44:23 +00:00
|
|
|
synchronized (GenericForegroundService.class) {
|
2019-06-27 16:18:52 +00:00
|
|
|
String action = intent.getAction();
|
|
|
|
if (ACTION_START.equals(action)) handleStart(intent);
|
|
|
|
else if (ACTION_STOP .equals(action)) handleStop(intent);
|
|
|
|
else throw new IllegalStateException(String.format("Action needs to be %s or %s.", ACTION_START, ACTION_STOP));
|
|
|
|
|
|
|
|
updateNotification();
|
2018-02-02 00:01:24 +00:00
|
|
|
|
2018-10-09 00:44:23 +00:00
|
|
|
return START_NOT_STICKY;
|
|
|
|
}
|
2018-02-02 00:01:24 +00:00
|
|
|
}
|
|
|
|
|
2019-06-27 16:18:52 +00:00
|
|
|
private synchronized void updateNotification() {
|
|
|
|
Iterator<Entry> iterator = allActiveMessages.values().iterator();
|
2018-02-02 00:01:24 +00:00
|
|
|
|
2019-06-27 16:18:52 +00:00
|
|
|
if (iterator.hasNext()) {
|
|
|
|
postObligatoryForegroundNotification(iterator.next());
|
|
|
|
} else {
|
2019-07-01 19:12:34 +00:00
|
|
|
Log.i(TAG, "Last request. Ending foreground service.");
|
2019-06-27 16:18:52 +00:00
|
|
|
postObligatoryForegroundNotification(lastPosted != null ? lastPosted : DEFAULTS);
|
|
|
|
stopForeground(true);
|
|
|
|
stopSelf();
|
|
|
|
}
|
|
|
|
}
|
2018-11-19 21:25:16 +00:00
|
|
|
|
2019-06-27 16:18:52 +00:00
|
|
|
private synchronized void handleStart(@NonNull Intent intent) {
|
|
|
|
Entry entry = Entry.fromIntent(intent);
|
2018-10-09 00:44:23 +00:00
|
|
|
|
2019-06-27 16:18:52 +00:00
|
|
|
Log.i(TAG, String.format(Locale.US, "handleStart() %s", entry));
|
2018-10-09 00:44:23 +00:00
|
|
|
|
2019-06-27 16:18:52 +00:00
|
|
|
allActiveMessages.put(entry.id, entry);
|
2018-02-02 00:01:24 +00:00
|
|
|
}
|
|
|
|
|
2019-06-27 16:18:52 +00:00
|
|
|
private synchronized void handleStop(@NonNull Intent intent) {
|
2018-11-19 21:25:16 +00:00
|
|
|
Log.i(TAG, "handleStop()");
|
|
|
|
|
2019-06-27 16:18:52 +00:00
|
|
|
int id = intent.getIntExtra(EXTRA_ID, -1);
|
2018-10-09 00:44:23 +00:00
|
|
|
|
2019-06-27 16:18:52 +00:00
|
|
|
Entry removed = allActiveMessages.remove(id);
|
2018-10-09 00:44:23 +00:00
|
|
|
|
2019-06-27 16:18:52 +00:00
|
|
|
if (removed == null) {
|
|
|
|
Log.w(TAG, "Could not find entry to remove");
|
2018-02-02 00:01:24 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-06-27 16:18:52 +00:00
|
|
|
private void postObligatoryForegroundNotification(@NonNull Entry active) {
|
|
|
|
lastPosted = active;
|
2019-11-14 19:35:08 +00:00
|
|
|
// TODO [greyson] Navigation
|
2019-06-27 16:18:52 +00:00
|
|
|
startForeground(NOTIFICATION_ID, new NotificationCompat.Builder(this, active.channelId)
|
|
|
|
.setSmallIcon(active.iconRes)
|
|
|
|
.setContentTitle(active.title)
|
|
|
|
.setProgress(active.progressMax, active.progress, active.indeterminate)
|
2020-12-19 20:54:00 +00:00
|
|
|
.setContentIntent(PendingIntent.getActivity(this, 0, MainActivity.clearTop(this), 0))
|
2018-10-09 00:44:23 +00:00
|
|
|
.build());
|
|
|
|
}
|
|
|
|
|
2018-02-02 00:01:24 +00:00
|
|
|
@Override
|
|
|
|
public IBinder onBind(Intent intent) {
|
2019-06-27 16:18:52 +00:00
|
|
|
return binder;
|
2018-02-02 00:01:24 +00:00
|
|
|
}
|
|
|
|
|
2019-06-27 16:18:52 +00:00
|
|
|
public static NotificationController startForegroundTask(@NonNull Context context, @NonNull String task) {
|
|
|
|
return startForegroundTask(context, task, DEFAULTS.channelId);
|
2018-08-06 16:20:24 +00:00
|
|
|
}
|
|
|
|
|
2019-06-27 16:18:52 +00:00
|
|
|
public static NotificationController startForegroundTask(@NonNull Context context, @NonNull String task, @NonNull String channelId) {
|
|
|
|
return startForegroundTask(context, task, channelId, DEFAULTS.iconRes);
|
2018-10-15 23:56:24 +00:00
|
|
|
}
|
|
|
|
|
2019-06-27 16:18:52 +00:00
|
|
|
public static NotificationController startForegroundTask(@NonNull Context context, @NonNull String task, @NonNull String channelId, @DrawableRes int iconRes) {
|
|
|
|
final int id = NEXT_ID.getAndIncrement();
|
|
|
|
|
2018-02-02 00:01:24 +00:00
|
|
|
Intent intent = new Intent(context, GenericForegroundService.class);
|
|
|
|
intent.setAction(ACTION_START);
|
|
|
|
intent.putExtra(EXTRA_TITLE, task);
|
2018-08-06 16:20:24 +00:00
|
|
|
intent.putExtra(EXTRA_CHANNEL_ID, channelId);
|
2018-10-15 23:56:24 +00:00
|
|
|
intent.putExtra(EXTRA_ICON_RES, iconRes);
|
2019-06-27 16:18:52 +00:00
|
|
|
intent.putExtra(EXTRA_ID, id);
|
2018-02-02 00:01:24 +00:00
|
|
|
|
2018-10-09 00:44:23 +00:00
|
|
|
ContextCompat.startForegroundService(context, intent);
|
2019-06-27 16:18:52 +00:00
|
|
|
|
|
|
|
return new NotificationController(context, id);
|
2018-02-02 00:01:24 +00:00
|
|
|
}
|
|
|
|
|
2019-06-27 16:18:52 +00:00
|
|
|
public static void stopForegroundTask(@NonNull Context context, int id) {
|
2018-02-02 00:01:24 +00:00
|
|
|
Intent intent = new Intent(context, GenericForegroundService.class);
|
|
|
|
intent.setAction(ACTION_STOP);
|
2019-06-27 16:18:52 +00:00
|
|
|
intent.putExtra(EXTRA_ID, id);
|
2018-02-02 00:01:24 +00:00
|
|
|
|
2018-10-09 00:44:23 +00:00
|
|
|
ContextCompat.startForegroundService(context, intent);
|
2018-02-02 00:01:24 +00:00
|
|
|
}
|
2019-06-27 16:18:52 +00:00
|
|
|
|
|
|
|
synchronized void replaceProgress(int id, int progressMax, int progress, boolean indeterminate) {
|
|
|
|
Entry oldEntry = allActiveMessages.get(id);
|
|
|
|
|
|
|
|
if (oldEntry == null) {
|
|
|
|
Log.w(TAG, "Failed to replace notification, it was not found");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Entry newEntry = new Entry(oldEntry.title, oldEntry.channelId, oldEntry.iconRes, oldEntry.id, progressMax, progress, indeterminate);
|
|
|
|
|
|
|
|
if (oldEntry.equals(newEntry)) {
|
|
|
|
Log.d(TAG, String.format("handleReplace() skip, no change %s", newEntry));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
Log.i(TAG, String.format("handleReplace() %s", newEntry));
|
|
|
|
|
|
|
|
allActiveMessages.put(newEntry.id, newEntry);
|
|
|
|
|
|
|
|
updateNotification();
|
|
|
|
}
|
|
|
|
|
|
|
|
private static class Entry {
|
|
|
|
final @NonNull String title;
|
|
|
|
final @NonNull String channelId;
|
|
|
|
final int id;
|
|
|
|
final @DrawableRes int iconRes;
|
|
|
|
final int progress;
|
|
|
|
final int progressMax;
|
|
|
|
final boolean indeterminate;
|
|
|
|
|
|
|
|
private Entry(@NonNull String title, @NonNull String channelId, @DrawableRes int iconRes, int id, int progressMax, int progress, boolean indeterminate) {
|
|
|
|
this.title = title;
|
|
|
|
this.channelId = channelId;
|
|
|
|
this.iconRes = iconRes;
|
|
|
|
this.id = id;
|
|
|
|
this.progress = progress;
|
|
|
|
this.progressMax = progressMax;
|
|
|
|
this.indeterminate = indeterminate;
|
|
|
|
}
|
|
|
|
|
|
|
|
private static Entry fromIntent(@NonNull Intent intent) {
|
|
|
|
int id = intent.getIntExtra(EXTRA_ID, DEFAULTS.id);
|
|
|
|
|
|
|
|
String title = intent.getStringExtra(EXTRA_TITLE);
|
|
|
|
if (title == null) title = DEFAULTS.title;
|
|
|
|
|
|
|
|
String channelId = intent.getStringExtra(EXTRA_CHANNEL_ID);
|
|
|
|
if (channelId == null) channelId = DEFAULTS.channelId;
|
|
|
|
|
|
|
|
int iconRes = intent.getIntExtra(EXTRA_ICON_RES, DEFAULTS.iconRes);
|
|
|
|
int progress = intent.getIntExtra(EXTRA_PROGRESS, DEFAULTS.progress);
|
|
|
|
int progressMax = intent.getIntExtra(EXTRA_PROGRESS_MAX, DEFAULTS.progressMax);
|
|
|
|
boolean indeterminate = intent.getBooleanExtra(EXTRA_PROGRESS_INDETERMINATE, DEFAULTS.indeterminate);
|
|
|
|
|
|
|
|
return new Entry(title, channelId, iconRes, id, progressMax, progress, indeterminate);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public @NonNull String toString() {
|
|
|
|
return String.format(Locale.US, "ChannelId: %s Id: %d Progress: %d/%d %s", channelId, id, progress, progressMax, indeterminate ? "indeterminate" : "determinate");
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean equals(Object o) {
|
|
|
|
if (this == o) return true;
|
|
|
|
if (o == null || getClass() != o.getClass()) return false;
|
|
|
|
|
|
|
|
Entry entry = (Entry) o;
|
|
|
|
return id == entry.id &&
|
|
|
|
iconRes == entry.iconRes &&
|
|
|
|
progress == entry.progress &&
|
|
|
|
progressMax == entry.progressMax &&
|
|
|
|
indeterminate == entry.indeterminate &&
|
|
|
|
Objects.equals(title, entry.title) &&
|
|
|
|
Objects.equals(channelId, entry.channelId);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
public int hashCode() {
|
|
|
|
int hashCode = title.hashCode();
|
|
|
|
hashCode *= 31;
|
|
|
|
hashCode += channelId.hashCode();
|
|
|
|
hashCode *= 31;
|
|
|
|
hashCode += id;
|
|
|
|
hashCode *= 31;
|
|
|
|
hashCode += iconRes;
|
|
|
|
hashCode *= 31;
|
|
|
|
hashCode += progress;
|
|
|
|
hashCode *= 31;
|
|
|
|
hashCode += progressMax;
|
|
|
|
hashCode *= 31;
|
|
|
|
hashCode += indeterminate ? 1 : 0;
|
|
|
|
return hashCode;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
class LocalBinder extends Binder {
|
|
|
|
GenericForegroundService getService() {
|
|
|
|
// Return this instance of LocalService so clients can call public methods
|
|
|
|
return GenericForegroundService.this;
|
|
|
|
}
|
|
|
|
}
|
2018-02-02 00:01:24 +00:00
|
|
|
}
|