mirror of
https://github.com/oxen-io/session-android.git
synced 2025-12-26 16:26:40 +00:00
Allow multiple messages on the Generic Foreground Service. Show the oldest still active.
This commit is contained in:
@@ -40,8 +40,8 @@ public class SQLCipherMigrationHelper {
|
||||
@NonNull net.sqlcipher.database.SQLiteDatabase modernDb)
|
||||
{
|
||||
modernDb.beginTransaction();
|
||||
int foregroundId = GenericForegroundService.startForegroundTask(context, context.getString(R.string.SQLCipherMigrationHelper_migrating_signal_database)).getId();
|
||||
try {
|
||||
GenericForegroundService.startForegroundTask(context, context.getString(R.string.SQLCipherMigrationHelper_migrating_signal_database));
|
||||
copyTable("identities", legacyDb, modernDb, null);
|
||||
copyTable("push", legacyDb, modernDb, null);
|
||||
copyTable("groups", legacyDb, modernDb, null);
|
||||
@@ -50,7 +50,7 @@ public class SQLCipherMigrationHelper {
|
||||
modernDb.setTransactionSuccessful();
|
||||
} finally {
|
||||
modernDb.endTransaction();
|
||||
GenericForegroundService.stopForegroundTask(context);
|
||||
GenericForegroundService.stopForegroundTask(context, foregroundId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,8 +65,8 @@ public class SQLCipherMigrationHelper {
|
||||
|
||||
modernDb.beginTransaction();
|
||||
|
||||
int foregroundId = GenericForegroundService.startForegroundTask(context, context.getString(R.string.SQLCipherMigrationHelper_migrating_signal_database)).getId();
|
||||
try {
|
||||
GenericForegroundService.startForegroundTask(context, context.getString(R.string.SQLCipherMigrationHelper_migrating_signal_database));
|
||||
int total = 5000;
|
||||
|
||||
copyTable("sms", legacyDb, modernDb, (row, progress) -> {
|
||||
@@ -175,7 +175,7 @@ public class SQLCipherMigrationHelper {
|
||||
modernDb.setTransactionSuccessful();
|
||||
} finally {
|
||||
modernDb.endTransaction();
|
||||
GenericForegroundService.stopForegroundTask(context);
|
||||
GenericForegroundService.stopForegroundTask(context, foregroundId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import org.greenrobot.eventbus.EventBus;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.attachments.Attachment;
|
||||
import org.thoughtcrime.securesms.attachments.AttachmentId;
|
||||
import org.thoughtcrime.securesms.attachments.DatabaseAttachment;
|
||||
@@ -19,6 +21,8 @@ import org.thoughtcrime.securesms.mms.MediaConstraints;
|
||||
import org.thoughtcrime.securesms.mms.MediaStream;
|
||||
import org.thoughtcrime.securesms.mms.MmsException;
|
||||
import org.thoughtcrime.securesms.mms.PartAuthority;
|
||||
import org.thoughtcrime.securesms.service.GenericForegroundService;
|
||||
import org.thoughtcrime.securesms.service.NotificationController;
|
||||
import org.thoughtcrime.securesms.transport.UndeliverableMessageException;
|
||||
import org.thoughtcrime.securesms.util.MediaUtil;
|
||||
import org.whispersystems.libsignal.util.guava.Optional;
|
||||
@@ -41,6 +45,11 @@ public class AttachmentUploadJob extends BaseJob implements InjectableType {
|
||||
private static final String KEY_ROW_ID = "row_id";
|
||||
private static final String KEY_UNIQUE_ID = "unique_id";
|
||||
|
||||
/**
|
||||
* Foreground notification shows while uploading attachments above this.
|
||||
*/
|
||||
private static final int FOREGROUND_LIMIT = 10 * 1024 * 1024;
|
||||
|
||||
private AttachmentId attachmentId;
|
||||
@Inject SignalServiceMessageSender messageSender;
|
||||
|
||||
@@ -79,13 +88,24 @@ public class AttachmentUploadJob extends BaseJob implements InjectableType {
|
||||
throw new IllegalStateException("Cannot find the specified attachment.");
|
||||
}
|
||||
|
||||
MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints();
|
||||
Attachment scaledAttachment = scaleAndStripExif(database, mediaConstraints, databaseAttachment);
|
||||
SignalServiceAttachment localAttachment = getAttachmentFor(scaledAttachment);
|
||||
SignalServiceAttachmentPointer remoteAttachment = messageSender.uploadAttachment(localAttachment.asStream(), databaseAttachment.isSticker());
|
||||
Attachment attachment = PointerAttachment.forPointer(Optional.of(remoteAttachment), null, databaseAttachment.getFastPreflightId()).get();
|
||||
MediaConstraints mediaConstraints = MediaConstraints.getPushMediaConstraints();
|
||||
Attachment scaledAttachment = scaleAndStripExif(database, mediaConstraints, databaseAttachment);
|
||||
|
||||
database.updateAttachmentAfterUpload(databaseAttachment.getAttachmentId(), attachment);
|
||||
try (NotificationController notification = getNotificationForAttachment(scaledAttachment)) {
|
||||
SignalServiceAttachment localAttachment = getAttachmentFor(scaledAttachment, notification);
|
||||
SignalServiceAttachmentPointer remoteAttachment = messageSender.uploadAttachment(localAttachment.asStream(), databaseAttachment.isSticker());
|
||||
Attachment attachment = PointerAttachment.forPointer(Optional.of(remoteAttachment), null, databaseAttachment.getFastPreflightId()).get();
|
||||
|
||||
database.updateAttachmentAfterUpload(databaseAttachment.getAttachmentId(), attachment);
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable NotificationController getNotificationForAttachment(@NonNull Attachment attachment) {
|
||||
if (attachment.getSize() >= FOREGROUND_LIMIT) {
|
||||
return GenericForegroundService.startForegroundTask(context, context.getString(R.string.AttachmentUploadJob_uploading_media));
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -96,7 +116,7 @@ public class AttachmentUploadJob extends BaseJob implements InjectableType {
|
||||
return exception instanceof IOException;
|
||||
}
|
||||
|
||||
private SignalServiceAttachment getAttachmentFor(Attachment attachment) {
|
||||
private SignalServiceAttachment getAttachmentFor(Attachment attachment, @Nullable NotificationController notification) {
|
||||
try {
|
||||
if (attachment.getDataUri() == null || attachment.getSize() == 0) throw new IOException("Assertion failed, outgoing attachment has no data!");
|
||||
InputStream is = PartAuthority.getAttachmentStream(context, attachment.getDataUri());
|
||||
@@ -109,7 +129,12 @@ public class AttachmentUploadJob extends BaseJob implements InjectableType {
|
||||
.withWidth(attachment.getWidth())
|
||||
.withHeight(attachment.getHeight())
|
||||
.withCaption(attachment.getCaption())
|
||||
.withListener((total, progress) -> EventBus.getDefault().postSticky(new PartProgressEvent(attachment, total, progress)))
|
||||
.withListener((total, progress) -> {
|
||||
EventBus.getDefault().postSticky(new PartProgressEvent(attachment, total, progress));
|
||||
if (notification != null) {
|
||||
notification.setProgress(total, progress);
|
||||
}
|
||||
})
|
||||
.build();
|
||||
} catch (IOException ioe) {
|
||||
Log.w(TAG, "Couldn't open attachment", ioe);
|
||||
|
||||
@@ -2,21 +2,22 @@ package org.thoughtcrime.securesms.jobs;
|
||||
|
||||
|
||||
import android.Manifest;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import org.thoughtcrime.securesms.backup.BackupPassphrase;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.backup.BackupPassphrase;
|
||||
import org.thoughtcrime.securesms.backup.FullBackupExporter;
|
||||
import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider;
|
||||
import org.thoughtcrime.securesms.database.DatabaseFactory;
|
||||
import org.thoughtcrime.securesms.database.NoExternalStorageException;
|
||||
import org.thoughtcrime.securesms.jobmanager.Data;
|
||||
import org.thoughtcrime.securesms.jobmanager.Job;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.thoughtcrime.securesms.permissions.Permissions;
|
||||
import org.thoughtcrime.securesms.service.GenericForegroundService;
|
||||
import org.thoughtcrime.securesms.service.NotificationController;
|
||||
import org.thoughtcrime.securesms.util.BackupUtil;
|
||||
import org.thoughtcrime.securesms.util.StorageUtil;
|
||||
|
||||
@@ -62,12 +63,13 @@ public class LocalBackupJob extends BaseJob {
|
||||
throw new IOException("No external storage permission!");
|
||||
}
|
||||
|
||||
GenericForegroundService.startForegroundTask(context,
|
||||
context.getString(R.string.LocalBackupJob_creating_backup),
|
||||
NotificationChannels.BACKUPS,
|
||||
R.drawable.ic_signal_backup);
|
||||
try (NotificationController notification = GenericForegroundService.startForegroundTask(context,
|
||||
context.getString(R.string.LocalBackupJob_creating_backup),
|
||||
NotificationChannels.BACKUPS,
|
||||
R.drawable.ic_signal_backup))
|
||||
{
|
||||
notification.setIndeterminateProgress();
|
||||
|
||||
try {
|
||||
String backupPassword = BackupPassphrase.get(context);
|
||||
File backupDirectory = StorageUtil.getBackupDirectory();
|
||||
String timestamp = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss", Locale.US).format(new Date());
|
||||
@@ -96,8 +98,6 @@ public class LocalBackupJob extends BaseJob {
|
||||
}
|
||||
|
||||
BackupUtil.deleteOldBackups();
|
||||
} finally {
|
||||
GenericForegroundService.stopForegroundTask(context);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
package org.thoughtcrime.securesms.service;
|
||||
|
||||
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
|
||||
import androidx.annotation.DrawableRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
@@ -16,111 +17,237 @@ import org.thoughtcrime.securesms.ConversationListActivity;
|
||||
import org.thoughtcrime.securesms.R;
|
||||
import org.thoughtcrime.securesms.logging.Log;
|
||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||
import org.whispersystems.libsignal.util.guava.Preconditions;
|
||||
|
||||
public class GenericForegroundService extends Service {
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
private static final String TAG = GenericForegroundService.class.getSimpleName();
|
||||
public final class GenericForegroundService extends Service {
|
||||
|
||||
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 TAG = Log.tag(GenericForegroundService.class);
|
||||
|
||||
private final IBinder binder = new LocalBinder();
|
||||
|
||||
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";
|
||||
|
||||
private static final String ACTION_START = "start";
|
||||
private static final String ACTION_STOP = "stop";
|
||||
|
||||
private int foregroundCount;
|
||||
private String activeTitle;
|
||||
private String activeChannelId;
|
||||
private int activeIconRes;
|
||||
private static final AtomicInteger NEXT_ID = new AtomicInteger();
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
private final LinkedHashMap<Integer, Entry> allActiveMessages = new LinkedHashMap<>();
|
||||
|
||||
}
|
||||
private static final Entry DEFAULTS = new Entry("", NotificationChannels.OTHER, R.drawable.ic_signal_grey_24dp, -1, 0, 0, false);
|
||||
|
||||
private @Nullable Entry lastPosted;
|
||||
|
||||
@Override
|
||||
public int onStartCommand(Intent intent, int flags, int startId) {
|
||||
if (intent == null) {
|
||||
throw new IllegalStateException("Intent needs to be non-null.");
|
||||
}
|
||||
|
||||
synchronized (GenericForegroundService.class) {
|
||||
if (intent != null && ACTION_START.equals(intent.getAction())) handleStart(intent);
|
||||
else if (intent != null && ACTION_STOP.equals(intent.getAction())) handleStop();
|
||||
else throw new IllegalStateException("Action needs to be START or STOP.");
|
||||
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();
|
||||
|
||||
return START_NOT_STICKY;
|
||||
}
|
||||
}
|
||||
|
||||
private synchronized void updateNotification() {
|
||||
Iterator<Entry> iterator = allActiveMessages.values().iterator();
|
||||
|
||||
private void handleStart(@NonNull Intent intent) {
|
||||
String title = Preconditions.checkNotNull(intent.getStringExtra(EXTRA_TITLE));
|
||||
String channelId = Preconditions.checkNotNull(intent.getStringExtra(EXTRA_CHANNEL_ID));
|
||||
int iconRes = intent.getIntExtra(EXTRA_ICON_RES, R.drawable.ic_signal_grey_24dp);
|
||||
|
||||
Log.i(TAG, "handleStart() Title: " + title + " ChannelId: " + channelId);
|
||||
|
||||
foregroundCount++;
|
||||
|
||||
if (foregroundCount == 1) {
|
||||
Log.d(TAG, "First request. Title: " + title + " ChannelId: " + channelId);
|
||||
activeTitle = title;
|
||||
activeChannelId = channelId;
|
||||
activeIconRes = iconRes;
|
||||
}
|
||||
|
||||
postObligatoryForegroundNotification(activeTitle, activeChannelId, activeIconRes);
|
||||
}
|
||||
|
||||
private void handleStop() {
|
||||
Log.i(TAG, "handleStop()");
|
||||
|
||||
postObligatoryForegroundNotification(activeTitle, activeChannelId, activeIconRes);
|
||||
|
||||
foregroundCount--;
|
||||
|
||||
if (foregroundCount == 0) {
|
||||
if (iterator.hasNext()) {
|
||||
postObligatoryForegroundNotification(iterator.next());
|
||||
} else {
|
||||
Log.d(TAG, "Last request. Ending foreground service.");
|
||||
postObligatoryForegroundNotification(lastPosted != null ? lastPosted : DEFAULTS);
|
||||
stopForeground(true);
|
||||
stopSelf();
|
||||
}
|
||||
}
|
||||
|
||||
private void postObligatoryForegroundNotification(String title, String channelId, @DrawableRes int iconRes) {
|
||||
startForeground(NOTIFICATION_ID, new NotificationCompat.Builder(this, channelId)
|
||||
.setSmallIcon(iconRes)
|
||||
.setContentTitle(title)
|
||||
private synchronized void handleStart(@NonNull Intent intent) {
|
||||
Entry entry = Entry.fromIntent(intent);
|
||||
|
||||
Log.i(TAG, String.format(Locale.US, "handleStart() %s", entry));
|
||||
|
||||
allActiveMessages.put(entry.id, entry);
|
||||
}
|
||||
|
||||
private synchronized void handleStop(@NonNull Intent intent) {
|
||||
Log.i(TAG, "handleStop()");
|
||||
|
||||
int id = intent.getIntExtra(EXTRA_ID, -1);
|
||||
|
||||
Entry removed = allActiveMessages.remove(id);
|
||||
|
||||
if (removed == null) {
|
||||
Log.w(TAG, "Could not find entry to remove");
|
||||
}
|
||||
}
|
||||
|
||||
private void postObligatoryForegroundNotification(@NonNull Entry active) {
|
||||
lastPosted = active;
|
||||
startForeground(NOTIFICATION_ID, new NotificationCompat.Builder(this, active.channelId)
|
||||
.setSmallIcon(active.iconRes)
|
||||
.setContentTitle(active.title)
|
||||
.setProgress(active.progressMax, active.progress, active.indeterminate)
|
||||
.setContentIntent(PendingIntent.getActivity(this, 0, new Intent(this, ConversationListActivity.class), 0))
|
||||
.build());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
return binder;
|
||||
}
|
||||
|
||||
public static void startForegroundTask(@NonNull Context context, @NonNull String task) {
|
||||
startForegroundTask(context, task, NotificationChannels.OTHER);
|
||||
public static NotificationController startForegroundTask(@NonNull Context context, @NonNull String task) {
|
||||
return startForegroundTask(context, task, DEFAULTS.channelId);
|
||||
}
|
||||
|
||||
public static void startForegroundTask(@NonNull Context context, @NonNull String task, @NonNull String channelId) {
|
||||
startForegroundTask(context, task, channelId, R.drawable.ic_signal_grey_24dp);
|
||||
public static NotificationController startForegroundTask(@NonNull Context context, @NonNull String task, @NonNull String channelId) {
|
||||
return startForegroundTask(context, task, channelId, DEFAULTS.iconRes);
|
||||
}
|
||||
|
||||
public static void startForegroundTask(@NonNull Context context, @NonNull String task, @NonNull String channelId, @DrawableRes int iconRes) {
|
||||
public static NotificationController startForegroundTask(@NonNull Context context, @NonNull String task, @NonNull String channelId, @DrawableRes int iconRes) {
|
||||
final int id = NEXT_ID.getAndIncrement();
|
||||
|
||||
Intent intent = new Intent(context, GenericForegroundService.class);
|
||||
intent.setAction(ACTION_START);
|
||||
intent.putExtra(EXTRA_TITLE, task);
|
||||
intent.putExtra(EXTRA_CHANNEL_ID, channelId);
|
||||
intent.putExtra(EXTRA_ICON_RES, iconRes);
|
||||
intent.putExtra(EXTRA_ID, id);
|
||||
|
||||
ContextCompat.startForegroundService(context, intent);
|
||||
|
||||
return new NotificationController(context, id);
|
||||
}
|
||||
|
||||
public static void stopForegroundTask(@NonNull Context context) {
|
||||
public static void stopForegroundTask(@NonNull Context context, int id) {
|
||||
Intent intent = new Intent(context, GenericForegroundService.class);
|
||||
intent.setAction(ACTION_STOP);
|
||||
intent.putExtra(EXTRA_ID, id);
|
||||
|
||||
ContextCompat.startForegroundService(context, intent);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
package org.thoughtcrime.securesms.service;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.IBinder;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public final class NotificationController implements AutoCloseable {
|
||||
|
||||
private final @NonNull Context context;
|
||||
private final int id;
|
||||
|
||||
private int progress;
|
||||
private int progressMax;
|
||||
private boolean indeterminate;
|
||||
private long percent = -1;
|
||||
|
||||
private final AtomicReference<GenericForegroundService> service = new AtomicReference<>();
|
||||
|
||||
NotificationController(@NonNull Context context, int id) {
|
||||
this.context = context;
|
||||
this.id = id;
|
||||
|
||||
bindToService();
|
||||
}
|
||||
|
||||
private void bindToService() {
|
||||
context.bindService(new Intent(context, GenericForegroundService.class), new ServiceConnection() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName name, IBinder service) {
|
||||
GenericForegroundService.LocalBinder binder = (GenericForegroundService.LocalBinder) service;
|
||||
GenericForegroundService genericForegroundService = binder.getService();
|
||||
|
||||
NotificationController.this.service.set(genericForegroundService);
|
||||
|
||||
updateProgressOnService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName name) {
|
||||
service.set(null);
|
||||
}
|
||||
}, Context.BIND_AUTO_CREATE);
|
||||
}
|
||||
|
||||
public int getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
GenericForegroundService.stopForegroundTask(context, id);
|
||||
}
|
||||
|
||||
public void setIndeterminateProgress() {
|
||||
setProgress(0, 0, true);
|
||||
}
|
||||
|
||||
public void setProgress(long newProgressMax, long newProgress) {
|
||||
setProgress((int) newProgressMax, (int) newProgress, false);
|
||||
}
|
||||
|
||||
private synchronized void setProgress(int newProgressMax, int newProgress, boolean indeterminant) {
|
||||
int newPercent = newProgressMax != 0 ? 100 * newProgress / newProgressMax : -1;
|
||||
|
||||
boolean same = newPercent == percent && indeterminate == indeterminant;
|
||||
|
||||
percent = newPercent;
|
||||
progress = newProgress;
|
||||
progressMax = newProgressMax;
|
||||
indeterminate = indeterminant;
|
||||
|
||||
if (same) return;
|
||||
|
||||
updateProgressOnService();
|
||||
}
|
||||
|
||||
private synchronized void updateProgressOnService() {
|
||||
GenericForegroundService genericForegroundService = service.get();
|
||||
|
||||
if (genericForegroundService == null) return;
|
||||
|
||||
genericForegroundService.replaceProgress(id, progressMax, progress, indeterminate);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user