Skip to content

Commit

Permalink
Periodic alarm to check for messages.
Browse files Browse the repository at this point in the history
  • Loading branch information
alan-signal authored and cody-signal committed Feb 10, 2021
1 parent 29d66f2 commit 98cb6b4
Show file tree
Hide file tree
Showing 14 changed files with 349 additions and 61 deletions.
7 changes: 7 additions & 0 deletions app/src/main/AndroidManifest.xml
Expand Up @@ -771,6 +771,13 @@
</intent-filter>
</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">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
Expand Down
Expand Up @@ -51,6 +51,7 @@
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;
Expand Down Expand Up @@ -312,6 +313,7 @@ private void initializePeriodicTasks() {
DirectoryRefreshListener.schedule(this);
LocalBackupListener.schedule(this);
RotateSenderCertificateListener.schedule(this);
MessageProcessReceiver.startOrUpdateAlarm(this);

if (BuildConfig.PLAY_STORE_DISABLED) {
UpdateApkRefreshListener.schedule(this);
Expand Down Expand Up @@ -377,7 +379,7 @@ private void initializePendingMessages() {
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);
}
Expand Down
Expand Up @@ -78,7 +78,7 @@ protected void onResume() {
super.onResume();

if (networkAccess.isCensored(this)) {
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob(this));
ApplicationDependencies.getJobManager().add(new PushNotificationReceiveJob());
}
}

Expand Down
Expand Up @@ -92,7 +92,7 @@ static void retrieveMessages(@NonNull Context context) {
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());
}
}
}
Expand Down
@@ -1,7 +1,5 @@
package org.thoughtcrime.securesms.jobs;

import android.content.Context;

import androidx.annotation.NonNull;

import org.signal.core.util.logging.Log;
Expand All @@ -15,29 +13,43 @@

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);

private static final String KEY_FOREGROUND_SERVICE_DELAY = "foreground_delay";

private final long foregroundServiceDelayMs;

public PushNotificationReceiveJob() {
this(BackgroundMessageRetriever.DO_NOT_SHOW_IN_FOREGROUND);
}

public PushNotificationReceiveJob(Context context) {
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
Expand All @@ -48,7 +60,7 @@ private PushNotificationReceiveJob(@NonNull Job.Parameters parameters) {
@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.");
Expand All @@ -69,14 +81,11 @@ public void onFailure() {
// MessageNotifier.notifyMessagesPending(getContext());
}

private static String timeSuffix(long startTime) {
return " (" + (System.currentTimeMillis() - startTime) + " ms elapsed)";
}

public static final class Factory implements Job.Factory<PushNotificationReceiveJob> {
@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));
}
}
}
@@ -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);
}
}
}
Expand Up @@ -8,8 +8,11 @@

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;
Expand All @@ -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);

This comment has been minimized.

Copy link
@szyszka90

szyszka90 Mar 9, 2021

@cody-signal Hi! I'm not Android programmer, but it seems that DO_NOT_SHOW_IN_FOREGROUND value doesn't work as expected in some cases - https://www.reddit.com/r/signal/comments/lo6ub8/checking_messages/

Of course maybe I interpreted wrong foreground meaning :) but I've found here the BackgroundMessageRetriever_checking_for_messages and suspect that it is connected.

This comment has been minimized.

Copy link
@szyszka90

szyszka90 Mar 9, 2021

@alan-signal rather interesting for you as the author

This comment has been minimized.

Copy link
@greyson-signal

greyson-signal Mar 9, 2021

Contributor

There's different overloads of this method that are called -- DO_NOT_SHOW_IN_FOREGROUND works as expected, but we will show a foreground notification in other situations, such as in the overload below :)

}

/**
* @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;
Expand All @@ -47,27 +60,29 @@ public boolean retrieveMessages(@NonNull Context context, MessageRetrievalStrate
}

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();
}
}
}
}
Expand Down
Expand Up @@ -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());
}
}

0 comments on commit 98cb6b4

Please sign in to comment.