Periodic alarm to check for messages.
This commit is contained in:
parent
29d66f2b92
commit
98cb6b457c
14 changed files with 349 additions and 61 deletions
|
@ -771,6 +771,13 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</receiver>
|
</receiver>
|
||||||
|
|
||||||
|
<receiver android:name=".messageprocessingalarm.MessageProcessReceiver">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
<action android:name="org.thoughtcrime.securesms.action.PROCESS_MESSAGES" />
|
||||||
|
</intent-filter>
|
||||||
|
</receiver>
|
||||||
|
|
||||||
<receiver android:name=".service.LocalBackupListener">
|
<receiver android:name=".service.LocalBackupListener">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
<action android:name="android.intent.action.BOOT_COMPLETED" />
|
||||||
|
|
|
@ -51,6 +51,7 @@ import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
|
import org.thoughtcrime.securesms.logging.CustomSignalProtocolLogger;
|
||||||
import org.thoughtcrime.securesms.logging.LogSecretProvider;
|
import org.thoughtcrime.securesms.logging.LogSecretProvider;
|
||||||
|
import org.thoughtcrime.securesms.messageprocessingalarm.MessageProcessReceiver;
|
||||||
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
import org.thoughtcrime.securesms.migrations.ApplicationMigrations;
|
||||||
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
import org.thoughtcrime.securesms.notifications.NotificationChannels;
|
||||||
import org.thoughtcrime.securesms.providers.BlobProvider;
|
import org.thoughtcrime.securesms.providers.BlobProvider;
|
||||||
|
@ -312,6 +313,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||||
DirectoryRefreshListener.schedule(this);
|
DirectoryRefreshListener.schedule(this);
|
||||||
LocalBackupListener.schedule(this);
|
LocalBackupListener.schedule(this);
|
||||||
RotateSenderCertificateListener.schedule(this);
|
RotateSenderCertificateListener.schedule(this);
|
||||||
|
MessageProcessReceiver.startOrUpdateAlarm(this);
|
||||||
|
|
||||||
if (BuildConfig.PLAY_STORE_DISABLED) {
|
if (BuildConfig.PLAY_STORE_DISABLED) {
|
||||||
UpdateApkRefreshListener.schedule(this);
|
UpdateApkRefreshListener.schedule(this);
|
||||||
|
@ -377,7 +379,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
|
||||||
if (Build.VERSION.SDK_INT >= 26) {
|
if (Build.VERSION.SDK_INT >= 26) {
|
||||||
FcmJobService.schedule(this);
|
FcmJobService.schedule(this);
|
||||||
} else {
|
} else {
|
||||||
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob(this));
|
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob());
|
||||||
}
|
}
|
||||||
TextSecurePreferences.setNeedsMessagePull(this, false);
|
TextSecurePreferences.setNeedsMessagePull(this, false);
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,7 +78,7 @@ public abstract class PassphraseRequiredActivity extends BaseActivity implements
|
||||||
super.onResume();
|
super.onResume();
|
||||||
|
|
||||||
if (networkAccess.isCensored(this)) {
|
if (networkAccess.isCensored(this)) {
|
||||||
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob(this));
|
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -92,7 +92,7 @@ public class FcmFetchService extends Service {
|
||||||
FcmJobService.schedule(context);
|
FcmJobService.schedule(context);
|
||||||
} else {
|
} else {
|
||||||
Log.w(TAG, "Failed to retrieve messages. Scheduling on JobManager (API " + Build.VERSION.SDK_INT + ").");
|
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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
package org.thoughtcrime.securesms.jobs;
|
package org.thoughtcrime.securesms.jobs;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
|
@ -15,29 +13,43 @@ import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
public class PushNotificationReceiveJob extends BaseJob {
|
public final class PushNotificationReceiveJob extends BaseJob {
|
||||||
|
|
||||||
public static final String KEY = "PushNotificationReceiveJob";
|
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()
|
this(new Job.Parameters.Builder()
|
||||||
.addConstraint(NetworkConstraint.KEY)
|
.addConstraint(NetworkConstraint.KEY)
|
||||||
.setQueue("__notification_received")
|
.setQueue("__notification_received")
|
||||||
.setMaxAttempts(3)
|
.setMaxAttempts(3)
|
||||||
.setMaxInstancesForFactory(1)
|
.setMaxInstancesForFactory(1)
|
||||||
.build());
|
.build(),
|
||||||
setContext(context);
|
foregroundServiceDelayMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PushNotificationReceiveJob(@NonNull Job.Parameters parameters) {
|
private PushNotificationReceiveJob(@NonNull Job.Parameters parameters, long foregroundServiceDelayMs) {
|
||||||
super(parameters);
|
super(parameters);
|
||||||
|
this.foregroundServiceDelayMs = foregroundServiceDelayMs;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Job withDelayedForegroundService(long foregroundServiceAfterMs) {
|
||||||
|
return new PushNotificationReceiveJob(foregroundServiceAfterMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public @NonNull Data serialize() {
|
public @NonNull Data serialize() {
|
||||||
return Data.EMPTY;
|
return new Data.Builder().putLong(KEY_FOREGROUND_SERVICE_DELAY, foregroundServiceDelayMs)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -48,7 +60,7 @@ public class PushNotificationReceiveJob extends BaseJob {
|
||||||
@Override
|
@Override
|
||||||
public void onRun() throws IOException {
|
public void onRun() throws IOException {
|
||||||
BackgroundMessageRetriever retriever = ApplicationDependencies.getBackgroundMessageRetriever();
|
BackgroundMessageRetriever retriever = ApplicationDependencies.getBackgroundMessageRetriever();
|
||||||
boolean result = retriever.retrieveMessages(context, new RestStrategy());
|
boolean result = retriever.retrieveMessages(context, foregroundServiceDelayMs, new RestStrategy());
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
Log.i(TAG, "Successfully pulled messages.");
|
Log.i(TAG, "Successfully pulled messages.");
|
||||||
|
@ -69,14 +81,11 @@ public class PushNotificationReceiveJob extends BaseJob {
|
||||||
// MessageNotifier.notifyMessagesPending(getContext());
|
// MessageNotifier.notifyMessagesPending(getContext());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String timeSuffix(long startTime) {
|
|
||||||
return " (" + (System.currentTimeMillis() - startTime) + " ms elapsed)";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static final class Factory implements Job.Factory<PushNotificationReceiveJob> {
|
public static final class Factory implements Job.Factory<PushNotificationReceiveJob> {
|
||||||
@Override
|
@Override
|
||||||
public @NonNull PushNotificationReceiveJob create(@NonNull Parameters parameters, @NonNull Data data) {
|
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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<JobTracker.JobState> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,8 +8,11 @@ import androidx.annotation.WorkerThread;
|
||||||
|
|
||||||
import org.signal.core.util.logging.Log;
|
import org.signal.core.util.logging.Log;
|
||||||
import org.thoughtcrime.securesms.ApplicationContext;
|
import org.thoughtcrime.securesms.ApplicationContext;
|
||||||
|
import org.thoughtcrime.securesms.R;
|
||||||
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
|
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.PowerManagerCompat;
|
||||||
import org.thoughtcrime.securesms.util.ServiceUtil;
|
import org.thoughtcrime.securesms.util.ServiceUtil;
|
||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
import org.thoughtcrime.securesms.util.TextSecurePreferences;
|
||||||
|
@ -31,11 +34,21 @@ public class BackgroundMessageRetriever {
|
||||||
|
|
||||||
private static final long NORMAL_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
|
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.
|
* @return False if the retrieval failed and should be rescheduled, otherwise true.
|
||||||
*/
|
*/
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
public boolean retrieveMessages(@NonNull Context context, MessageRetrievalStrategy... strategies) {
|
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)) {
|
if (shouldIgnoreFetch(context)) {
|
||||||
Log.i(TAG, "Skipping retrieval -- app is in the foreground.");
|
Log.i(TAG, "Skipping retrieval -- app is in the foreground.");
|
||||||
return true;
|
return true;
|
||||||
|
@ -47,6 +60,7 @@ public class BackgroundMessageRetriever {
|
||||||
}
|
}
|
||||||
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
|
try (DelayedNotificationController controller = GenericForegroundService.startForegroundTaskDelayed(context, context.getString(R.string.BackgroundMessageRetriever_checking_for_messages), showNotificationAfterMs)) {
|
||||||
PowerManager.WakeLock wakeLock = null;
|
PowerManager.WakeLock wakeLock = null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -71,6 +85,7 @@ public class BackgroundMessageRetriever {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean executeBackgroundRetrieval(@NonNull Context context, long startTime, @NonNull MessageRetrievalStrategy[] strategies) {
|
private boolean executeBackgroundRetrieval(@NonNull Context context, long startTime, @NonNull MessageRetrievalStrategy[] strategies) {
|
||||||
boolean success = false;
|
boolean success = false;
|
||||||
|
|
|
@ -11,6 +11,6 @@ public class BootReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob(context));
|
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
* <p>
|
||||||
|
* With this, you can {@link #close()} it to dismiss or prevent it showing if it hasn't already.
|
||||||
|
* <p>
|
||||||
|
* 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.
|
||||||
|
* <p>
|
||||||
|
* 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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -58,11 +58,14 @@ public final class GenericForegroundService extends Service {
|
||||||
|
|
||||||
synchronized (GenericForegroundService.class) {
|
synchronized (GenericForegroundService.class) {
|
||||||
String action = intent.getAction();
|
String action = intent.getAction();
|
||||||
|
|
||||||
|
if (action != null) {
|
||||||
if (ACTION_START.equals(action)) handleStart(intent);
|
if (ACTION_START.equals(action)) handleStart(intent);
|
||||||
else if (ACTION_STOP .equals(action)) handleStop(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));
|
else throw new IllegalStateException(String.format("Action needs to be %s or %s.", ACTION_START, ACTION_STOP));
|
||||||
|
|
||||||
updateNotification();
|
updateNotification();
|
||||||
|
}
|
||||||
|
|
||||||
return START_NOT_STICKY;
|
return START_NOT_STICKY;
|
||||||
}
|
}
|
||||||
|
@ -117,6 +120,15 @@ public final class GenericForegroundService extends Service {
|
||||||
return binder;
|
return binder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Waits for {@param delayMillis} ms before starting the foreground task.
|
||||||
|
* <p>
|
||||||
|
* 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) {
|
public static NotificationController startForegroundTask(@NonNull Context context, @NonNull String task) {
|
||||||
return startForegroundTask(context, task, DEFAULTS.channelId);
|
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_ICON_RES, iconRes);
|
||||||
intent.putExtra(EXTRA_ID, id);
|
intent.putExtra(EXTRA_ID, id);
|
||||||
|
|
||||||
|
Log.i(TAG, String.format(Locale.US, "Starting foreground service (%s) id=%d", task, id));
|
||||||
ContextCompat.startForegroundService(context, intent);
|
ContextCompat.startForegroundService(context, intent);
|
||||||
|
|
||||||
return new NotificationController(context, id);
|
return new NotificationController(context, id);
|
||||||
|
@ -145,6 +158,7 @@ public final class GenericForegroundService extends Service {
|
||||||
intent.setAction(ACTION_STOP);
|
intent.setAction(ACTION_STOP);
|
||||||
intent.putExtra(EXTRA_ID, id);
|
intent.putExtra(EXTRA_ID, id);
|
||||||
|
|
||||||
|
Log.i(TAG, String.format(Locale.US, "Stopping foreground service id=%d", id));
|
||||||
ContextCompat.startForegroundService(context, intent);
|
ContextCompat.startForegroundService(context, intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,11 +8,16 @@ import android.os.IBinder;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import org.signal.core.util.logging.Log;
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
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 Context context;
|
||||||
private final int id;
|
private final int id;
|
||||||
|
|
||||||
private int progress;
|
private int progress;
|
||||||
|
@ -30,22 +35,7 @@ public final class NotificationController implements AutoCloseable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void bindToService() {
|
private void bindToService() {
|
||||||
context.bindService(new Intent(context, GenericForegroundService.class), new ServiceConnection() {
|
context.bindService(new Intent(context, GenericForegroundService.class), this, Context.BIND_AUTO_CREATE);
|
||||||
@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() {
|
public int getId() {
|
||||||
|
@ -54,6 +44,7 @@ public final class NotificationController implements AutoCloseable {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
|
context.unbindService(this);
|
||||||
GenericForegroundService.stopForegroundTask(context, id);
|
GenericForegroundService.stopForegroundTask(context, id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,4 +78,23 @@ public final class NotificationController implements AutoCloseable {
|
||||||
|
|
||||||
genericForegroundService.replaceProgress(id, progressMax, progress, indeterminate);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,8 @@ public abstract class PersistentAlarmManagerListener extends BroadcastReceiver {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
|
Log.i(TAG, String.format("%s#onReceive(%s)", getClass().getSimpleName(), intent.getAction()));
|
||||||
|
|
||||||
long scheduledTime = getNextScheduledExecutionTime(context);
|
long scheduledTime = getNextScheduledExecutionTime(context);
|
||||||
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
|
||||||
Intent alarmIntent = new Intent(context, getClass());
|
Intent alarmIntent = new Intent(context, getClass());
|
||||||
|
@ -27,7 +29,7 @@ public abstract class PersistentAlarmManagerListener extends BroadcastReceiver {
|
||||||
scheduledTime = onAlarm(context, scheduledTime);
|
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.cancel(pendingIntent);
|
||||||
alarmManager.set(AlarmManager.RTC_WAKEUP, scheduledTime, pendingIntent);
|
alarmManager.set(AlarmManager.RTC_WAKEUP, scheduledTime, pendingIntent);
|
||||||
|
|
|
@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.util;
|
||||||
|
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
|
import android.util.TimeUtils;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.VisibleForTesting;
|
import androidx.annotation.VisibleForTesting;
|
||||||
|
@ -16,6 +17,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
|
||||||
import org.thoughtcrime.securesms.groups.SelectionLimits;
|
import org.thoughtcrime.securesms.groups.SelectionLimits;
|
||||||
import org.thoughtcrime.securesms.jobs.RemoteConfigRefreshJob;
|
import org.thoughtcrime.securesms.jobs.RemoteConfigRefreshJob;
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
import org.thoughtcrime.securesms.keyvalue.SignalStore;
|
||||||
|
import org.thoughtcrime.securesms.messageprocessingalarm.MessageProcessReceiver;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
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 SHARE_SELECTION_LIMIT = "android.share.limit";
|
||||||
private static final String ANIMATED_STICKER_MIN_MEMORY = "android.animatedStickerMinMemory";
|
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 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
|
* 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,
|
OKHTTP_AUTOMATIC_RETRY,
|
||||||
SHARE_SELECTION_LIMIT,
|
SHARE_SELECTION_LIMIT,
|
||||||
ANIMATED_STICKER_MIN_MEMORY,
|
ANIMATED_STICKER_MIN_MEMORY,
|
||||||
ANIMATED_STICKER_MIN_TOTAL_MEMORY
|
ANIMATED_STICKER_MIN_TOTAL_MEMORY,
|
||||||
|
MESSAGE_PROCESSOR_ALARM_INTERVAL
|
||||||
);
|
);
|
||||||
|
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
|
@ -148,7 +152,8 @@ public final class FeatureFlags {
|
||||||
OKHTTP_AUTOMATIC_RETRY,
|
OKHTTP_AUTOMATIC_RETRY,
|
||||||
SHARE_SELECTION_LIMIT,
|
SHARE_SELECTION_LIMIT,
|
||||||
ANIMATED_STICKER_MIN_MEMORY,
|
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.
|
* desired test state.
|
||||||
*/
|
*/
|
||||||
private static final Map<String, OnFlagChange> FLAG_CHANGE_LISTENERS = new HashMap<String, OnFlagChange>() {{
|
private static final Map<String, OnFlagChange> FLAG_CHANGE_LISTENERS = new HashMap<String, OnFlagChange>() {{
|
||||||
|
put(MESSAGE_PROCESSOR_ALARM_INTERVAL, change -> MessageProcessReceiver.startOrUpdateAlarm(ApplicationDependencies.getApplication()));
|
||||||
}};
|
}};
|
||||||
|
|
||||||
private static final Map<String, Object> REMOTE_VALUES = new TreeMap<>();
|
private static final Map<String, Object> 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 {
|
private enum VersionFlag {
|
||||||
/** The flag is no set */
|
/** The flag is no set */
|
||||||
OFF,
|
OFF,
|
||||||
|
|
|
@ -82,6 +82,9 @@
|
||||||
<string name="AttachmentUploadJob_uploading_media">Uploading media…</string>
|
<string name="AttachmentUploadJob_uploading_media">Uploading media…</string>
|
||||||
<string name="AttachmentUploadJob_compressing_video_start">Compressing video…</string>
|
<string name="AttachmentUploadJob_compressing_video_start">Compressing video…</string>
|
||||||
|
|
||||||
|
<!-- BackgroundMessageRetriever -->
|
||||||
|
<string name="BackgroundMessageRetriever_checking_for_messages">Checking for messages…</string>
|
||||||
|
|
||||||
<!-- BlockedUsersActivity -->
|
<!-- BlockedUsersActivity -->
|
||||||
<string name="BlockedUsersActivity__blocked_users">Blocked users</string>
|
<string name="BlockedUsersActivity__blocked_users">Blocked users</string>
|
||||||
<string name="BlockedUsersActivity__add_blocked_user">Add blocked user</string>
|
<string name="BlockedUsersActivity__add_blocked_user">Add blocked user</string>
|
||||||
|
|
Loading…
Add table
Reference in a new issue