diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index b70c498adf..d3321ca127 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -683,6 +683,12 @@
+
+
Reply
Signal Message
Unsecured SMS
- Pending Signal messages
- You have pending Signal messages, tap to open and retrieve
+ You may have new messages
+ Open Signal to check for recent notifications.
%1$s %2$s
Contact
diff --git a/src/org/thoughtcrime/securesms/ApplicationContext.java b/src/org/thoughtcrime/securesms/ApplicationContext.java
index 143d05571d..5266d98935 100644
--- a/src/org/thoughtcrime/securesms/ApplicationContext.java
+++ b/src/org/thoughtcrime/securesms/ApplicationContext.java
@@ -36,6 +36,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.dependencies.AxolotlStorageModule;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule;
+import org.thoughtcrime.securesms.gcm.FcmJobService;
import org.thoughtcrime.securesms.jobmanager.DependencyInjector;
import org.thoughtcrime.securesms.jobmanager.JobManager;
import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer;
@@ -135,6 +136,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
Log.i(TAG, "App is now visible.");
executePendingContactSync();
KeyCachingService.onAppForegrounded(this);
+ MessageNotifier.cancelMessagesPending(this);
}
@Override
@@ -334,7 +336,11 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc
private void initializePendingMessages() {
if (TextSecurePreferences.getNeedsMessagePull(this)) {
Log.i(TAG, "Scheduling a message fetch.");
- ApplicationContext.getInstance(this).getJobManager().add(new PushNotificationReceiveJob(this));
+ if (Build.VERSION.SDK_INT >= 26) {
+ FcmJobService.schedule(this);
+ } else {
+ ApplicationContext.getInstance(this).getJobManager().add(new PushNotificationReceiveJob(this));
+ }
TextSecurePreferences.setNeedsMessagePull(this, false);
}
}
diff --git a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java
index ffa43b236b..cfa41d2eed 100644
--- a/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java
+++ b/src/org/thoughtcrime/securesms/dependencies/SignalCommunicationModule.java
@@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.CreateProfileActivity;
import org.thoughtcrime.securesms.DeviceListFragment;
import org.thoughtcrime.securesms.crypto.storage.SignalProtocolStoreImpl;
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
+import org.thoughtcrime.securesms.gcm.FcmJobService;
import org.thoughtcrime.securesms.gcm.FcmService;
import org.thoughtcrime.securesms.jobs.AttachmentDownloadJob;
import org.thoughtcrime.securesms.jobs.AttachmentUploadJob;
@@ -108,7 +109,8 @@ import dagger.Provides;
StickerPackDownloadJob.class,
MultiDeviceStickerPackOperationJob.class,
MultiDeviceStickerPackSyncJob.class,
- LinkPreviewRepository.class})
+ LinkPreviewRepository.class,
+ FcmJobService.class})
public class SignalCommunicationModule {
private static final String TAG = SignalCommunicationModule.class.getSimpleName();
diff --git a/src/org/thoughtcrime/securesms/gcm/FcmJobService.java b/src/org/thoughtcrime/securesms/gcm/FcmJobService.java
new file mode 100644
index 0000000000..c0154f77d1
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/gcm/FcmJobService.java
@@ -0,0 +1,77 @@
+package org.thoughtcrime.securesms.gcm;
+
+import android.app.job.JobInfo;
+import android.app.job.JobParameters;
+import android.app.job.JobService;
+import android.content.ComponentName;
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import org.thoughtcrime.securesms.ApplicationContext;
+import org.thoughtcrime.securesms.dependencies.InjectableType;
+import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
+import org.thoughtcrime.securesms.logging.Log;
+import org.thoughtcrime.securesms.notifications.MessageNotifier;
+import org.thoughtcrime.securesms.util.ServiceUtil;
+import org.thoughtcrime.securesms.util.TextSecurePreferences;
+import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
+import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
+
+import java.io.IOException;
+
+import javax.inject.Inject;
+
+/**
+ * Pulls down messages. Used when we fail to pull down messages in {@link FcmService}.
+ */
+@RequiresApi(26)
+public class FcmJobService extends JobService implements InjectableType {
+
+ private static final String TAG = FcmJobService.class.getSimpleName();
+
+ private static final int ID = 1337;
+
+ @Inject SignalServiceMessageReceiver messageReceiver;
+
+ @RequiresApi(26)
+ public static void schedule(@NonNull Context context) {
+ JobInfo.Builder jobInfoBuilder = new JobInfo.Builder(ID, new ComponentName(context, FcmJobService.class))
+ .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
+ .setBackoffCriteria(0, JobInfo.BACKOFF_POLICY_LINEAR)
+ .setPersisted(true);
+
+ ServiceUtil.getJobScheduler(context).schedule(jobInfoBuilder.build());
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ Log.d(TAG, "onStartJob()");
+ ApplicationContext.getInstance(getApplicationContext()).injectDependencies(this);
+
+ if (ApplicationContext.getInstance(getApplicationContext()).isAppVisible()) {
+ Log.i(TAG, "App is foregrounded. No need to run.");
+ return false;
+ }
+
+ SignalExecutors.UNBOUNDED.execute(() -> {
+ try {
+ new PushNotificationReceiveJob(getApplicationContext()).pullAndProcessMessages(messageReceiver, TAG, System.currentTimeMillis());
+ jobFinished(params, false);
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to pull. Notifying and scheduling a retry.", e);
+ MessageNotifier.notifyMessagesPending(getApplicationContext());
+ jobFinished(params, true);
+ }
+ });
+
+ return true;
+ }
+
+ @Override
+ public boolean onStopJob(JobParameters params) {
+ Log.d(TAG, "onStopJob()");
+ return TextSecurePreferences.getNeedsMessagePull(getApplicationContext());
+ }
+}
diff --git a/src/org/thoughtcrime/securesms/gcm/FcmService.java b/src/org/thoughtcrime/securesms/gcm/FcmService.java
index 12a1f2f891..6f0fd1f5e3 100644
--- a/src/org/thoughtcrime/securesms/gcm/FcmService.java
+++ b/src/org/thoughtcrime/securesms/gcm/FcmService.java
@@ -1,33 +1,26 @@
package org.thoughtcrime.securesms.gcm;
import android.content.Context;
+import android.os.Build;
import android.os.PowerManager;
-import androidx.annotation.NonNull;
import com.google.firebase.messaging.FirebaseMessagingService;
import com.google.firebase.messaging.RemoteMessage;
import org.thoughtcrime.securesms.ApplicationContext;
-import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.jobs.FcmRefreshJob;
import org.thoughtcrime.securesms.jobs.PushNotificationReceiveJob;
import org.thoughtcrime.securesms.logging.Log;
-import org.thoughtcrime.securesms.notifications.NotificationChannels;
-import org.thoughtcrime.securesms.service.GenericForegroundService;
import org.thoughtcrime.securesms.util.PowerManagerCompat;
import org.thoughtcrime.securesms.util.ServiceUtil;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.WakeLockUtil;
-import org.thoughtcrime.securesms.util.concurrent.SignalExecutors;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
-import org.whispersystems.signalservice.internal.util.Util;
import java.io.IOException;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.Executor;
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
@@ -35,8 +28,8 @@ public class FcmService extends FirebaseMessagingService implements InjectableTy
private static final String TAG = FcmService.class.getSimpleName();
- private static final Executor MESSAGE_EXECUTOR = SignalExecutors.newCachedSingleThreadExecutor("FcmMessageProcessing");
- private static final String WAKE_LOCK_TAG = "FcmMessageProcessing";
+ private static final String WAKE_LOCK_TAG = "FcmMessageProcessing";
+ private static final long SOCKET_TIMEOUT = TimeUnit.SECONDS.toMillis(10);
@Inject SignalServiceMessageReceiver messageReceiver;
@@ -79,69 +72,27 @@ public class FcmService extends FirebaseMessagingService implements InjectableTy
boolean doze = PowerManagerCompat.isDeviceIdleMode(powerManager);
boolean network = new NetworkConstraint.Factory(ApplicationContext.getInstance(context)).create().isMet();
- final Object foregroundLock = new Object();
- final AtomicBoolean foregroundRunning = new AtomicBoolean(false);
- final AtomicBoolean taskCompleted = new AtomicBoolean(false);
- final CountDownLatch latch = new CountDownLatch(1);
-
if (doze || !network) {
- Log.i(TAG, "Starting a foreground task because we may be operating in a constrained environment. Doze: " + doze + " Network: " + network);
- showForegroundNotification(context);
- foregroundRunning.set(true);
- latch.countDown();
- }
-
- MESSAGE_EXECUTOR.execute(() -> {
- try {
- new PushNotificationReceiveJob(context).pullAndProcessMessages(messageReceiver, TAG, startTime);
- } catch (IOException e) {
- Log.i(TAG, "Failed to retrieve the envelope. Scheduling on JobManager.", e);
- ApplicationContext.getInstance(context)
- .getJobManager()
- .add(new PushNotificationReceiveJob(context));
- } finally {
- synchronized (foregroundLock) {
- if (foregroundRunning.getAndSet(false)) {
- GenericForegroundService.stopForegroundTask(context);
- } else {
- latch.countDown();
- }
- taskCompleted.set(true);
- }
-
- decrementActiveGcmCount();
- Log.i(TAG, "Processing complete.");
- }
- });
-
- if (!foregroundRunning.get()) {
- new Thread("FcmForegroundServiceTimer") {
- @Override
- public void run() {
- Util.sleep(7000);
- synchronized (foregroundLock) {
- if (!taskCompleted.get() && !foregroundRunning.getAndSet(true)) {
- Log.i(TAG, "Starting a foreground task because the job is running long.");
- showForegroundNotification(context);
- latch.countDown();
- }
- }
- }
- }.start();
+ Log.w(TAG, "We may be operating in a constrained environment. Doze: " + doze + " Network: " + network);
}
try {
- latch.await();
- } catch (InterruptedException e) {
- Log.w(TAG, "Latch was interrupted.", e);
+ messageReceiver.setSoTimeoutMillis(SOCKET_TIMEOUT);
+ new PushNotificationReceiveJob(context).pullAndProcessMessages(messageReceiver, TAG, startTime);
+ } catch (IOException e) {
+ if (Build.VERSION.SDK_INT >= 26) {
+ Log.i(TAG, "Failed to retrieve the envelope. Scheduling on the system JobScheduler (API " + Build.VERSION.SDK_INT + ").", e);
+ FcmJobService.schedule(context);
+ } else {
+ Log.i(TAG, "Failed to retrieve the envelope. Scheduling on JobManager (API " + Build.VERSION.SDK_INT + ").", e);
+ ApplicationContext.getInstance(context)
+ .getJobManager()
+ .add(new PushNotificationReceiveJob(context));
+ }
}
- }
- private void showForegroundNotification(@NonNull Context context) {
- GenericForegroundService.startForegroundTask(context,
- context.getString(R.string.GcmBroadcastReceiver_retrieving_a_message),
- NotificationChannels.OTHER,
- R.drawable.ic_signal_downloading);
+ decrementActiveGcmCount();
+ Log.i(TAG, "Processing complete.");
}
private static synchronized boolean incrementActiveGcmCount() {
diff --git a/src/org/thoughtcrime/securesms/jobs/PushNotificationReceiveJob.java b/src/org/thoughtcrime/securesms/jobs/PushNotificationReceiveJob.java
index f421b5552b..757d596972 100644
--- a/src/org/thoughtcrime/securesms/jobs/PushNotificationReceiveJob.java
+++ b/src/org/thoughtcrime/securesms/jobs/PushNotificationReceiveJob.java
@@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.jobmanager.Data;
import org.thoughtcrime.securesms.jobmanager.Job;
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint;
import org.thoughtcrime.securesms.logging.Log;
+import org.thoughtcrime.securesms.notifications.MessageNotifier;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.signalservice.api.SignalServiceMessageReceiver;
import org.whispersystems.signalservice.api.push.exceptions.PushNetworkException;
@@ -61,6 +62,7 @@ public class PushNotificationReceiveJob extends PushReceivedJob implements Injec
Log.i(tag, "Successfully processed an envelope." + timeSuffix(startTime));
});
TextSecurePreferences.setNeedsMessagePull(context, false);
+ MessageNotifier.cancelMessagesPending(context);
}
}
@Override
diff --git a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java
index f563d4cb1e..9825afd0f6 100644
--- a/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java
+++ b/src/org/thoughtcrime/securesms/notifications/MessageNotifier.java
@@ -38,6 +38,7 @@ import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import android.text.TextUtils;
+import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.contactshare.Contact;
import org.thoughtcrime.securesms.contactshare.ContactUtil;
@@ -120,7 +121,7 @@ public class MessageNotifier {
}
public static void notifyMessagesPending(Context context) {
- if (!TextSecurePreferences.isNotificationsEnabled(context)) {
+ if (!TextSecurePreferences.isNotificationsEnabled(context) || ApplicationContext.getInstance(context).isAppVisible()) {
return;
}
@@ -128,6 +129,10 @@ public class MessageNotifier {
ServiceUtil.getNotificationManager(context).notify(PENDING_MESSAGES_ID, builder.build());
}
+ public static void cancelMessagesPending(Context context) {
+ ServiceUtil.getNotificationManager(context).cancel(PENDING_MESSAGES_ID);
+ }
+
public static void cancelDelayedNotifications() {
executor.cancel();
}
diff --git a/src/org/thoughtcrime/securesms/notifications/PendingMessageNotificationBuilder.java b/src/org/thoughtcrime/securesms/notifications/PendingMessageNotificationBuilder.java
index eca8a36913..fb07b7e99c 100644
--- a/src/org/thoughtcrime/securesms/notifications/PendingMessageNotificationBuilder.java
+++ b/src/org/thoughtcrime/securesms/notifications/PendingMessageNotificationBuilder.java
@@ -23,14 +23,16 @@ public class PendingMessageNotificationBuilder extends AbstractNotificationBuild
setColor(context.getResources().getColor(R.color.textsecure_primary));
setCategory(NotificationCompat.CATEGORY_MESSAGE);
- setContentTitle(context.getString(R.string.MessageNotifier_pending_signal_messages));
- setContentText(context.getString(R.string.MessageNotifier_you_have_pending_signal_messages));
- setTicker(context.getString(R.string.MessageNotifier_you_have_pending_signal_messages));
+ setContentTitle(context.getString(R.string.MessageNotifier_you_may_have_new_messages));
+ setContentText(context.getString(R.string.MessageNotifier_open_signal_to_check_for_recent_notifications));
+ setTicker(context.getString(R.string.MessageNotifier_open_signal_to_check_for_recent_notifications));
setContentIntent(PendingIntent.getActivity(context, 0, intent, 0));
setAutoCancel(true);
setAlarms(null, RecipientDatabase.VibrateState.DEFAULT);
+ setOnlyAlertOnce(true);
+
if (!NotificationChannels.supported()) {
setPriority(TextSecurePreferences.getNotificationPriority(context));
}
diff --git a/src/org/thoughtcrime/securesms/util/ServiceUtil.java b/src/org/thoughtcrime/securesms/util/ServiceUtil.java
index dd74ad1d33..a6cd2d1920 100644
--- a/src/org/thoughtcrime/securesms/util/ServiceUtil.java
+++ b/src/org/thoughtcrime/securesms/util/ServiceUtil.java
@@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.util;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.NotificationManager;
+import android.app.job.JobScheduler;
import android.content.Context;
import android.media.AudioManager;
import android.net.ConnectivityManager;
@@ -54,6 +55,11 @@ public class ServiceUtil {
return (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);
}
+ @RequiresApi(26)
+ public static JobScheduler getJobScheduler(Context context) {
+ return (JobScheduler) context.getSystemService(JobScheduler.class);
+ }
+
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
public static @Nullable SubscriptionManager getSubscriptionManager(@NonNull Context context) {
return (SubscriptionManager) context.getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE);