From c089d6cd4376baf32f12cce33bc2913c8b24a868 Mon Sep 17 00:00:00 2001 From: Alan Evans Date: Thu, 27 Jun 2019 12:18:52 -0400 Subject: [PATCH] Allow multiple messages on the Generic Foreground Service. Show the oldest still active. --- res/values/strings.xml | 5 +- .../helpers/SQLCipherMigrationHelper.java | 8 +- .../securesms/jobs/AttachmentUploadJob.java | 41 ++- .../securesms/jobs/LocalBackupJob.java | 24 +- .../service/GenericForegroundService.java | 241 +++++++++++++----- .../service/NotificationController.java | 90 +++++++ 6 files changed, 327 insertions(+), 82 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/service/NotificationController.java diff --git a/res/values/strings.xml b/res/values/strings.xml index eced3b8ac1..1f033ab244 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -63,13 +63,16 @@ (location) (reply) - + Can\'t find an app to select media. Signal requires the Storage permission in order to attach photos, videos, or audio, but it has been permanently denied. Please continue to the app settings menu, select \"Permissions\", and enable \"Storage\". Signal requires Contacts permission in order to attach contact information, but it has been permanently denied. Please continue to the app settings menu, select \"Permissions\", and enable \"Contacts\". Signal requires Location permission in order to attach a location, but it has been permanently denied. Please continue to the app settings menu, select \"Permissions\", and enable \"Location\". Signal requires the Camera permission in order to take photos, but it has been permanently denied. Please continue to the app settings menu, select \"Permissions\", and enable \"Camera\". + + Uploading media... + Error playing audio! diff --git a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherMigrationHelper.java b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherMigrationHelper.java index eba8140a78..02682b46b6 100644 --- a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherMigrationHelper.java +++ b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherMigrationHelper.java @@ -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); } } diff --git a/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java b/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java index ff9996d578..5b9f797eaa 100644 --- a/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java +++ b/src/org/thoughtcrime/securesms/jobs/AttachmentUploadJob.java @@ -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); diff --git a/src/org/thoughtcrime/securesms/jobs/LocalBackupJob.java b/src/org/thoughtcrime/securesms/jobs/LocalBackupJob.java index 4fd58672a3..d14fda05b2 100644 --- a/src/org/thoughtcrime/securesms/jobs/LocalBackupJob.java +++ b/src/org/thoughtcrime/securesms/jobs/LocalBackupJob.java @@ -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); } } diff --git a/src/org/thoughtcrime/securesms/service/GenericForegroundService.java b/src/org/thoughtcrime/securesms/service/GenericForegroundService.java index a0eea63670..b34c9b5120 100644 --- a/src/org/thoughtcrime/securesms/service/GenericForegroundService.java +++ b/src/org/thoughtcrime/securesms/service/GenericForegroundService.java @@ -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 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 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; + } + } } diff --git a/src/org/thoughtcrime/securesms/service/NotificationController.java b/src/org/thoughtcrime/securesms/service/NotificationController.java new file mode 100644 index 0000000000..625a7261e2 --- /dev/null +++ b/src/org/thoughtcrime/securesms/service/NotificationController.java @@ -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 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); + } +}