From 98cb6b457c543b36e2f6862e92f12185fb4fe364 Mon Sep 17 00:00:00 2001 From: Alan Evans Date: Wed, 10 Feb 2021 14:01:11 -0400 Subject: [PATCH] Periodic alarm to check for messages. --- app/src/main/AndroidManifest.xml | 7 + .../securesms/ApplicationContext.java | 4 +- .../securesms/PassphraseRequiredActivity.java | 2 +- .../securesms/gcm/FcmFetchService.java | 2 +- .../jobs/PushNotificationReceiveJob.java | 39 +++-- .../MessageProcessReceiver.java | 81 +++++++++++ .../messages/BackgroundMessageRetriever.java | 47 +++--- .../securesms/service/BootReceiver.java | 2 +- .../DelayedNotificationController.java | 134 ++++++++++++++++++ .../service/GenericForegroundService.java | 22 ++- .../service/NotificationController.java | 48 ++++--- .../PersistentAlarmManagerListener.java | 4 +- .../securesms/util/FeatureFlags.java | 15 +- app/src/main/res/values/strings.xml | 3 + 14 files changed, 349 insertions(+), 61 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/messageprocessingalarm/MessageProcessReceiver.java create mode 100644 app/src/main/java/org/thoughtcrime/securesms/service/DelayedNotificationController.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e4ad6b9fe4..7d0783e663 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -771,6 +771,13 @@ + + + + + + + diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 4451252a9e..08036f53f3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -51,6 +51,7 @@ import org.thoughtcrime.securesms.jobs.RetrieveProfileJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger; import org.thoughtcrime.securesms.logging.LogSecretProvider; +import org.thoughtcrime.securesms.messageprocessingalarm.MessageProcessReceiver; import org.thoughtcrime.securesms.migrations.ApplicationMigrations; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.providers.BlobProvider; @@ -312,6 +313,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr DirectoryRefreshListener.schedule(this); LocalBackupListener.schedule(this); RotateSenderCertificateListener.schedule(this); + MessageProcessReceiver.startOrUpdateAlarm(this); if (BuildConfig.PLAY_STORE_DISABLED) { UpdateApkRefreshListener.schedule(this); @@ -377,7 +379,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr if (Build.VERSION.SDK_INT >= 26) { FcmJobService.schedule(this); } else { - ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob(this)); + ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob()); } TextSecurePreferences.setNeedsMessagePull(this, false); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java b/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java index eb2eac2f31..08c5f02d41 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/PassphraseRequiredActivity.java @@ -78,7 +78,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements super.onResume(); if (networkAccess.isCensored(this)) { - ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob(this)); + ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob()); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchService.java b/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchService.java index 2cb758b27f..3f39ed4af9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/gcm/FcmFetchService.java @@ -92,7 +92,7 @@ public class FcmFetchService extends Service { FcmJobService.schedule(context); } else { Log.w(TAG, "Failed to retrieve messages. Scheduling on JobManager (API " + Build.VERSION.SDK_INT + ")."); - ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob(context)); + ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob()); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushNotificationReceiveJob.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushNotificationReceiveJob.java index c9d7ce79a5..20a4a2a0ac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/PushNotificationReceiveJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/PushNotificationReceiveJob.java @@ -1,7 +1,5 @@ package org.thoughtcrime.securesms.jobs; -import android.content.Context; - import androidx.annotation.NonNull; import org.signal.core.util.logging.Log; @@ -15,29 +13,43 @@ import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException import java.io.IOException; -public class PushNotificationReceiveJob extends BaseJob { +public final class PushNotificationReceiveJob extends BaseJob { public static final String KEY = "PushNotificationReceiveJob"; - private static final String TAG = PushNotificationReceiveJob.class.getSimpleName(); + private static final String TAG = Log.tag(PushNotificationReceiveJob.class); - public PushNotificationReceiveJob(Context context) { + private static final String KEY_FOREGROUND_SERVICE_DELAY = "foreground_delay"; + + private final long foregroundServiceDelayMs; + + public PushNotificationReceiveJob() { + this(BackgroundMessageRetriever.DO_NOT_SHOW_IN_FOREGROUND); + } + + private PushNotificationReceiveJob(long foregroundServiceDelayMs) { this(new Job.Parameters.Builder() .addConstraint(NetworkConstraint.KEY) .setQueue("__notification_received") .setMaxAttempts(3) .setMaxInstancesForFactory(1) - .build()); - setContext(context); + .build(), + foregroundServiceDelayMs); } - private PushNotificationReceiveJob(@NonNull Job.Parameters parameters) { + private PushNotificationReceiveJob(@NonNull Job.Parameters parameters, long foregroundServiceDelayMs) { super(parameters); + this.foregroundServiceDelayMs = foregroundServiceDelayMs; + } + + public static Job withDelayedForegroundService(long foregroundServiceAfterMs) { + return new PushNotificationReceiveJob(foregroundServiceAfterMs); } @Override public @NonNull Data serialize() { - return Data.EMPTY; + return new Data.Builder().putLong(KEY_FOREGROUND_SERVICE_DELAY, foregroundServiceDelayMs) + .build(); } @Override @@ -48,7 +60,7 @@ public class PushNotificationReceiveJob extends BaseJob { @Override public void onRun() throws IOException { BackgroundMessageRetriever retriever = ApplicationDependencies.getBackgroundMessageRetriever(); - boolean result = retriever.retrieveMessages(context, new RestStrategy()); + boolean result = retriever.retrieveMessages(context, foregroundServiceDelayMs, new RestStrategy()); if (result) { Log.i(TAG, "Successfully pulled messages."); @@ -69,14 +81,11 @@ public class PushNotificationReceiveJob extends BaseJob { // MessageNotifier.notifyMessagesPending(getContext()); } - private static String timeSuffix(long startTime) { - return " (" + (System.currentTimeMillis() - startTime) + " ms elapsed)"; - } - public static final class Factory implements Job.Factory { @Override public @NonNull PushNotificationReceiveJob create(@NonNull Parameters parameters, @NonNull Data data) { - return new PushNotificationReceiveJob(parameters); + return new PushNotificationReceiveJob(parameters, + data.getLongOrDefault(KEY_FOREGROUND_SERVICE_DELAY, BackgroundMessageRetriever.DO_NOT_SHOW_IN_FOREGROUND)); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/messageprocessingalarm/MessageProcessReceiver.java b/app/src/main/java/org/thoughtcrime/securesms/messageprocessingalarm/MessageProcessReceiver.java new file mode 100644 index 0000000000..f3a2733e64 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/messageprocessingalarm/MessageProcessReceiver.java @@ -0,0 +1,81 @@ +package org.thoughtcrime.securesms.messageprocessingalarm; + +import android.annotation.SuppressLint; +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.SystemClock; + +import androidx.annotation.NonNull; + +import org.signal.core.util.concurrent.SignalExecutors; +import org.signal.core.util.logging.Log; +import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; +import org.thoughtcrime.securesms.jobmanager.JobTracker; +import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob; +import org.thoughtcrime.securesms.util.FeatureFlags; +import org.whispersystems.libsignal.util.guava.Optional; + +import java.util.concurrent.TimeUnit; + +/** + * On received message, runs a job to poll for messages. + */ +public final class MessageProcessReceiver extends BroadcastReceiver { + + private static final String TAG = Log.tag(MessageProcessReceiver.class); + + private static final long FIRST_RUN_DELAY = TimeUnit.MINUTES.toMillis(3); + private static final long FOREGROUND_DELAY = TimeUnit.SECONDS.toMillis(2); + private static final long JOB_TIMEOUT = TimeUnit.SECONDS.toMillis(5); + + public static final String BROADCAST_ACTION = "org.thoughtcrime.securesms.action.PROCESS_MESSAGES"; + + @Override + @SuppressLint("StaticFieldLeak") + public void onReceive(@NonNull Context context, @NonNull Intent intent) { + Log.i(TAG, String.format("onReceive(%s)", intent.getAction())); + + if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) { + Log.i(TAG, "Starting Alarm because of boot receiver"); + startOrUpdateAlarm(context); + } else if (BROADCAST_ACTION.equals(intent.getAction())) { + PendingResult pendingResult = goAsync(); + + SignalExecutors.BOUNDED.submit(() -> { + Log.i(TAG, "Running PushNotificationReceiveJob"); + + Optional jobState = ApplicationDependencies.getJobManager() + .runSynchronously(PushNotificationReceiveJob.withDelayedForegroundService(FOREGROUND_DELAY), JOB_TIMEOUT); + + Log.i(TAG, "PushNotificationReceiveJob ended: " + (jobState.isPresent() ? jobState.get().toString() : "Job did not complete")); + + pendingResult.finish(); + }); + } + } + + public static void startOrUpdateAlarm(@NonNull Context context) { + Intent alarmIntent = new Intent(context, MessageProcessReceiver.class); + + alarmIntent.setAction(MessageProcessReceiver.BROADCAST_ACTION); + + PendingIntent pendingIntent = PendingIntent.getBroadcast(context, 123, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT); + AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); + + long interval = FeatureFlags.getBackgroundMessageProcessDelay(); + + if (interval < 0) { + alarmManager.cancel(pendingIntent); + Log.i(TAG, "Alarm cancelled"); + } else { + alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP, + SystemClock.elapsedRealtime() + FIRST_RUN_DELAY, + interval, + pendingIntent); + Log.i(TAG, "Alarm scheduled to repeat at interval " + interval); + } + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/messages/BackgroundMessageRetriever.java b/app/src/main/java/org/thoughtcrime/securesms/messages/BackgroundMessageRetriever.java index 8dd9564d24..b043ec1213 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messages/BackgroundMessageRetriever.java +++ b/app/src/main/java/org/thoughtcrime/securesms/messages/BackgroundMessageRetriever.java @@ -8,8 +8,11 @@ import androidx.annotation.WorkerThread; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.ApplicationContext; +import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint; +import org.thoughtcrime.securesms.service.DelayedNotificationController; +import org.thoughtcrime.securesms.service.GenericForegroundService; import org.thoughtcrime.securesms.util.PowerManagerCompat; import org.thoughtcrime.securesms.util.ServiceUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -31,11 +34,21 @@ public class BackgroundMessageRetriever { private static final long NORMAL_TIMEOUT = TimeUnit.SECONDS.toMillis(10); + public static final long DO_NOT_SHOW_IN_FOREGROUND = DelayedNotificationController.DO_NOT_SHOW; + /** * @return False if the retrieval failed and should be rescheduled, otherwise true. */ @WorkerThread public boolean retrieveMessages(@NonNull Context context, MessageRetrievalStrategy... strategies) { + return retrieveMessages(context, DO_NOT_SHOW_IN_FOREGROUND, strategies); + } + + /** + * @return False if the retrieval failed and should be rescheduled, otherwise true. + */ + @WorkerThread + public boolean retrieveMessages(@NonNull Context context, long showNotificationAfterMs, MessageRetrievalStrategy... strategies) { if (shouldIgnoreFetch(context)) { Log.i(TAG, "Skipping retrieval -- app is in the foreground."); return true; @@ -47,27 +60,29 @@ public class BackgroundMessageRetriever { } synchronized (this) { - PowerManager.WakeLock wakeLock = null; + try (DelayedNotificationController controller = GenericForegroundService.startForegroundTaskDelayed(context, context.getString(R.string.BackgroundMessageRetriever_checking_for_messages), showNotificationAfterMs)) { + PowerManager.WakeLock wakeLock = null; - try { - wakeLock = WakeLockUtil.acquire(context, PowerManager.PARTIAL_WAKE_LOCK, TimeUnit.SECONDS.toMillis(60), WAKE_LOCK_TAG); + try { + wakeLock = WakeLockUtil.acquire(context, PowerManager.PARTIAL_WAKE_LOCK, TimeUnit.SECONDS.toMillis(60), WAKE_LOCK_TAG); - TextSecurePreferences.setNeedsMessagePull(context, true); + TextSecurePreferences.setNeedsMessagePull(context, true); - long startTime = System.currentTimeMillis(); - PowerManager powerManager = ServiceUtil.getPowerManager(context); - boolean doze = PowerManagerCompat.isDeviceIdleMode(powerManager); - boolean network = new NetworkConstraint.Factory(ApplicationContext.getInstance(context)).create().isMet(); + long startTime = System.currentTimeMillis(); + PowerManager powerManager = ServiceUtil.getPowerManager(context); + boolean doze = PowerManagerCompat.isDeviceIdleMode(powerManager); + boolean network = new NetworkConstraint.Factory(ApplicationContext.getInstance(context)).create().isMet(); - if (doze || !network) { - Log.w(TAG, "We may be operating in a constrained environment. Doze: " + doze + " Network: " + network); + if (doze || !network) { + Log.w(TAG, "We may be operating in a constrained environment. Doze: " + doze + " Network: " + network); + } + + Log.i(TAG, "Performing normal message fetch."); + return executeBackgroundRetrieval(context, startTime, strategies); + } finally { + WakeLockUtil.release(wakeLock, WAKE_LOCK_TAG); + ACTIVE_LOCK.release(); } - - Log.i(TAG, "Performing normal message fetch."); - return executeBackgroundRetrieval(context, startTime, strategies); - } finally { - WakeLockUtil.release(wakeLock, WAKE_LOCK_TAG); - ACTIVE_LOCK.release(); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/BootReceiver.java b/app/src/main/java/org/thoughtcrime/securesms/service/BootReceiver.java index e7d9d8d416..d968878ac8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/BootReceiver.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/BootReceiver.java @@ -11,6 +11,6 @@ public class BootReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { - ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob(context)); + ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob()); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/DelayedNotificationController.java b/app/src/main/java/org/thoughtcrime/securesms/service/DelayedNotificationController.java new file mode 100644 index 0000000000..29ce6258b1 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/service/DelayedNotificationController.java @@ -0,0 +1,134 @@ +package org.thoughtcrime.securesms.service; + +import android.os.Handler; +import android.os.Looper; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.signal.core.util.concurrent.SignalExecutors; +import org.signal.core.util.logging.Log; + +import java.util.Objects; + +/** + * Represents a delayed foreground notification. + *

+ * With this, you can {@link #close()} it to dismiss or prevent it showing if it hasn't already. + *

+ * You can also {@link #showNow()} to show if it's not already. This returns a regular {@link NotificationController} which can be updated if required. + */ +public abstract class DelayedNotificationController implements AutoCloseable { + + private static final String TAG = Log.tag(DelayedNotificationController.class); + + public static final long SHOW_WITHOUT_DELAY = 0; + public static final long DO_NOT_SHOW = -1; + + private DelayedNotificationController() {} + + static DelayedNotificationController create(long delayMillis, @NonNull Create createTask) { + if (delayMillis == SHOW_WITHOUT_DELAY) return new Shown(createTask.create()); + if (delayMillis == DO_NOT_SHOW) return new NoShow(); + if (delayMillis > 0) return new DelayedShow(delayMillis, createTask); + + throw new IllegalArgumentException("Illegal delay " + delayMillis); + } + + /** + * Show the foreground notification if it's not already showing. + *

+ * If it does show, it returns a regular {@link NotificationController} which you can use to update its message or progress. + */ + public abstract @Nullable NotificationController showNow(); + + @Override + public void close() { + } + + private static final class NoShow extends DelayedNotificationController { + + @Override + public @Nullable NotificationController showNow() { + return null; + } + } + + private static final class Shown extends DelayedNotificationController { + + private final NotificationController controller; + + Shown(@NonNull NotificationController controller) { + this.controller = controller; + } + + @Override + public void close() { + this.controller.close(); + } + + @Override + public NotificationController showNow() { + return controller; + } + } + + private static final class DelayedShow extends DelayedNotificationController { + + private final Create createTask; + private final Handler handler; + private final Runnable start; + private NotificationController notificationController; + private boolean isClosed; + + private DelayedShow(long delayMillis, @NonNull Create createTask) { + this.createTask = createTask; + this.handler = new Handler(Looper.getMainLooper()); + this.start = this::start; + + handler.postDelayed(start, delayMillis); + } + + private void start() { + SignalExecutors.BOUNDED.execute(this::showNowInner); + } + + public synchronized @NonNull NotificationController showNow() { + if (isClosed) { + throw new AssertionError("showNow called after close"); + } + return Objects.requireNonNull(showNowInner()); + } + + private synchronized @Nullable NotificationController showNowInner() { + if (notificationController != null) { + return notificationController; + } + + if (!isClosed) { + Log.i(TAG, "Starting foreground service"); + notificationController = createTask.create(); + return notificationController; + } else { + Log.i(TAG, "Did not start foreground service as close has been called"); + return null; + } + } + + @Override + public synchronized void close() { + handler.removeCallbacks(start); + isClosed = true; + if (notificationController != null) { + Log.d(TAG, "Closing"); + notificationController.close(); + } else { + Log.d(TAG, "Never showed"); + } + } + } + + public interface Create { + @NonNull NotificationController create(); + } +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/GenericForegroundService.java b/app/src/main/java/org/thoughtcrime/securesms/service/GenericForegroundService.java index 69098fea89..568cd8c06f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/GenericForegroundService.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/GenericForegroundService.java @@ -58,11 +58,14 @@ public final class GenericForegroundService extends Service { synchronized (GenericForegroundService.class) { 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(); + if (action != null) { + 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; } @@ -117,6 +120,15 @@ public final class GenericForegroundService extends Service { return binder; } + /** + * Waits for {@param delayMillis} ms before starting the foreground task. + *

+ * The delayed notification controller can also shown on demand and promoted to a regular notification controller to update the message etc. + */ + public static DelayedNotificationController startForegroundTaskDelayed(@NonNull Context context, @NonNull String task, long delayMillis) { + return DelayedNotificationController.create(delayMillis, () -> startForegroundTask(context, task)); + } + public static NotificationController startForegroundTask(@NonNull Context context, @NonNull String task) { return startForegroundTask(context, task, DEFAULTS.channelId); } @@ -135,6 +147,7 @@ public final class GenericForegroundService extends Service { intent.putExtra(EXTRA_ICON_RES, iconRes); intent.putExtra(EXTRA_ID, id); + Log.i(TAG, String.format(Locale.US, "Starting foreground service (%s) id=%d", task, id)); ContextCompat.startForegroundService(context, intent); return new NotificationController(context, id); @@ -145,6 +158,7 @@ public final class GenericForegroundService extends Service { intent.setAction(ACTION_STOP); intent.putExtra(EXTRA_ID, id); + Log.i(TAG, String.format(Locale.US, "Stopping foreground service id=%d", id)); ContextCompat.startForegroundService(context, intent); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/NotificationController.java b/app/src/main/java/org/thoughtcrime/securesms/service/NotificationController.java index 625a7261e2..566964b8b7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/NotificationController.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/NotificationController.java @@ -8,12 +8,17 @@ import android.os.IBinder; import androidx.annotation.NonNull; +import org.signal.core.util.logging.Log; + import java.util.concurrent.atomic.AtomicReference; -public final class NotificationController implements AutoCloseable { +public final class NotificationController implements AutoCloseable, + ServiceConnection +{ + private static final String TAG = Log.tag(NotificationController.class); - private final @NonNull Context context; - private final int id; + private final Context context; + private final int id; private int progress; private int progressMax; @@ -30,22 +35,7 @@ public final class NotificationController implements AutoCloseable { } 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); + context.bindService(new Intent(context, GenericForegroundService.class), this, Context.BIND_AUTO_CREATE); } public int getId() { @@ -54,6 +44,7 @@ public final class NotificationController implements AutoCloseable { @Override public void close() { + context.unbindService(this); GenericForegroundService.stopForegroundTask(context, id); } @@ -87,4 +78,23 @@ public final class NotificationController implements AutoCloseable { genericForegroundService.replaceProgress(id, progressMax, progress, indeterminate); } + + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + Log.i(TAG, "Service connected " + name); + + GenericForegroundService.LocalBinder binder = (GenericForegroundService.LocalBinder) service; + GenericForegroundService genericForegroundService = binder.getService(); + + this.service.set(genericForegroundService); + + updateProgressOnService(); + } + + @Override + public void onServiceDisconnected(ComponentName name) { + Log.i(TAG, "Service disconnected " + name); + + service.set(null); + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/PersistentAlarmManagerListener.java b/app/src/main/java/org/thoughtcrime/securesms/service/PersistentAlarmManagerListener.java index fbdb3f9a9e..5aec76f97f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/PersistentAlarmManagerListener.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/PersistentAlarmManagerListener.java @@ -18,6 +18,8 @@ public abstract class PersistentAlarmManagerListener extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { + Log.i(TAG, String.format("%s#onReceive(%s)", getClass().getSimpleName(), intent.getAction())); + long scheduledTime = getNextScheduledExecutionTime(context); AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); Intent alarmIntent = new Intent(context, getClass()); @@ -27,7 +29,7 @@ public abstract class PersistentAlarmManagerListener extends BroadcastReceiver { scheduledTime = onAlarm(context, scheduledTime); } - Log.i(TAG, getClass() + " scheduling for: " + scheduledTime); + Log.i(TAG, getClass() + " scheduling for: " + scheduledTime + " action: " + intent.getAction()); alarmManager.cancel(pendingIntent); alarmManager.set(AlarmManager.RTC_WAKEUP, scheduledTime, pendingIntent); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java index 1082126993..143f65c747 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/FeatureFlags.java @@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.util; import android.os.Build; import android.text.TextUtils; +import android.util.TimeUtils; import androidx.annotation.NonNull; import androidx.annotation.VisibleForTesting; @@ -16,6 +17,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies; import org.thoughtcrime.securesms.groups.SelectionLimits; import org.thoughtcrime.securesms.jobs.RemoteConfigRefreshJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; +import org.thoughtcrime.securesms.messageprocessingalarm.MessageProcessReceiver; import java.util.HashMap; import java.util.HashSet; @@ -75,6 +77,7 @@ public final class FeatureFlags { private static final String SHARE_SELECTION_LIMIT = "android.share.limit"; private static final String ANIMATED_STICKER_MIN_MEMORY = "android.animatedStickerMinMemory"; private static final String ANIMATED_STICKER_MIN_TOTAL_MEMORY = "android.animatedStickerMinTotalMemory"; + private static final String MESSAGE_PROCESSOR_ALARM_INTERVAL = "android.messageProcessor.alarmIntervalMins"; /** * We will only store remote values for flags in this set. If you want a flag to be controllable @@ -106,7 +109,8 @@ public final class FeatureFlags { OKHTTP_AUTOMATIC_RETRY, SHARE_SELECTION_LIMIT, ANIMATED_STICKER_MIN_MEMORY, - ANIMATED_STICKER_MIN_TOTAL_MEMORY + ANIMATED_STICKER_MIN_TOTAL_MEMORY, + MESSAGE_PROCESSOR_ALARM_INTERVAL ); @VisibleForTesting @@ -148,7 +152,8 @@ public final class FeatureFlags { OKHTTP_AUTOMATIC_RETRY, SHARE_SELECTION_LIMIT, ANIMATED_STICKER_MIN_MEMORY, - ANIMATED_STICKER_MIN_TOTAL_MEMORY + ANIMATED_STICKER_MIN_TOTAL_MEMORY, + MESSAGE_PROCESSOR_ALARM_INTERVAL ); /** @@ -171,6 +176,7 @@ public final class FeatureFlags { * desired test state. */ private static final Map FLAG_CHANGE_LISTENERS = new HashMap() {{ + put(MESSAGE_PROCESSOR_ALARM_INTERVAL, change -> MessageProcessReceiver.startOrUpdateAlarm(ApplicationDependencies.getApplication())); }}; private static final Map REMOTE_VALUES = new TreeMap<>(); @@ -479,6 +485,11 @@ public final class FeatureFlags { } } + public static long getBackgroundMessageProcessDelay() { + int delayMinutes = getInteger(MESSAGE_PROCESSOR_ALARM_INTERVAL, (int) TimeUnit.HOURS.toMinutes(6)); + return TimeUnit.MINUTES.toMillis(delayMinutes); + } + private enum VersionFlag { /** The flag is no set */ OFF, diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8d206ece53..ed21d55bc5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -82,6 +82,9 @@ Uploading media… Compressing video… + + Checking for messages… + Blocked users Add blocked user