Show foreground notification for jobs when network is restricted.

Occasionally a job may be run when the app is in a network-restricted
mode, like a form of doze. When this happens, jobs can timeout due to
lack of network access, causing a cascade of job delays. This is
particularly bad in the case of message retrieval.

To prevent this, if a job that normally requires network detects that no
network is available when running, then we start a foreground
notification.
This commit is contained in:
Greyson Parrelli 2018-10-03 09:42:35 -07:00
parent c86c2c51bb
commit e4b56d4e40
5 changed files with 57 additions and 2 deletions

View File

@ -371,6 +371,9 @@
<string name="InviteActivity_no_app_to_share_to">It looks like you don\'t have any apps to share to.</string>
<string name="InviteActivity_friends_dont_let_friends_text_unencrypted">Friends don\'t let friends chat unencrypted.</string>
<!-- Job -->
<string name="Job_working_in_the_background">Working in the background...</string>
<!-- MessageDetailsRecipient -->
<string name="MessageDetailsRecipient_failed_to_send">Failed to send</string>
<string name="MessageDetailsRecipient_new_safety_number">New safety number</string>
@ -477,6 +480,9 @@
<!-- PlayServicesProblemFragment -->
<string name="PlayServicesProblemFragment_the_version_of_google_play_services_you_have_installed_is_not_functioning">The version of Google Play Services you have installed is not functioning correctly. Please reinstall Google Play Services and try again.</string>
<!-- PushNotificationReceiveJob -->
<string name="PushNotificationReceiveJob_retrieving_a_message">Retrieving a message...</string>
<!-- RatingManager -->
<string name="RatingManager_rate_this_app">Rate this app</string>
<string name="RatingManager_if_you_enjoy_using_this_app_please_take_a_moment">If you enjoy using this app, please take a moment to help us by rating it.</string>
@ -556,6 +562,9 @@
<string name="SearchFragment_header_contacts">Contacts</string>
<string name="SearchFragment_header_messages">Messages</string>
<!-- SendJob -->
<string name="SendJob_sending_a_message">Sending a message...</string>
<!-- SharedContactDetailsActivity -->
<string name="SharedContactDetailsActivity_add_to_contacts">Add to Contacts</string>
<string name="SharedContactDetailsActivity_invite_to_signal">Invite to Signal</string>

View File

@ -5,10 +5,13 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import org.thoughtcrime.securesms.ApplicationContext;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.jobmanager.dependencies.ContextDependent;
import org.thoughtcrime.securesms.jobmanager.requirements.NetworkRequirement;
import org.thoughtcrime.securesms.jobs.requirements.MasterSecretRequirement;
import org.thoughtcrime.securesms.jobs.requirements.SqlCipherMigrationRequirement;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.service.GenericForegroundService;
import java.io.Serializable;
import java.util.UUID;
@ -26,6 +29,7 @@ public abstract class Job extends Worker implements Serializable {
static final String KEY_RETRY_COUNT = "Job_retry_count";
static final String KEY_RETRY_UNTIL = "Job_retry_until";
static final String KEY_SUBMIT_TIME = "Job_submit_time";
static final String KEY_REQUIRES_NETWORK = "Job_requires_network";
static final String KEY_REQUIRES_MASTER_SECRET = "Job_requires_master_secret";
static final String KEY_REQUIRES_SQLCIPHER = "Job_requires_sqlcipher";
@ -58,10 +62,19 @@ public abstract class Job extends Worker implements Serializable {
initialize(new SafeData(data));
boolean foregroundRunning = false;
try {
if (withinRetryLimits(data)) {
if (requirementsMet(data)) {
if (needsForegroundService(data)) {
Log.i(TAG, "Running a foreground service with description '" + getDescription() + "' to aid in job execution." + logSuffix());
GenericForegroundService.startForegroundTask(getApplicationContext(), getDescription());
foregroundRunning = true;
}
onRun();
log("Successfully completed." + logSuffix());
return Result.SUCCESS;
} else {
@ -79,6 +92,11 @@ public abstract class Job extends Worker implements Serializable {
}
warn("Failing due to an exception." + logSuffix(), e);
return cancel();
} finally {
if (foregroundRunning) {
Log.i(TAG, "Stopping the foreground service." + logSuffix());
GenericForegroundService.stopForegroundTask(getApplicationContext());
}
}
}
@ -95,6 +113,14 @@ public abstract class Job extends Worker implements Serializable {
onAdded();
}
/**
* @return A string that represents what the task does. Will be shown in a foreground notification
* if necessary.
*/
protected String getDescription() {
return getApplicationContext().getString(R.string.Job_working_in_the_background);
}
/**
* Called after a run has finished and we've determined a retry is required, but before the next
* attempt is run.
@ -158,7 +184,7 @@ public abstract class Job extends Worker implements Serializable {
return Result.SUCCESS;
}
private boolean requirementsMet(Data data) {
private boolean requirementsMet(@NonNull Data data) {
boolean met = true;
if (data.getBoolean(KEY_REQUIRES_MASTER_SECRET, false)) {
@ -172,7 +198,7 @@ public abstract class Job extends Worker implements Serializable {
return met;
}
private boolean withinRetryLimits(Data data) {
private boolean withinRetryLimits(@NonNull Data data) {
int retryCount = data.getInt(KEY_RETRY_COUNT, 0);
long retryUntil = data.getLong(KEY_RETRY_UNTIL, 0);
@ -183,6 +209,13 @@ public abstract class Job extends Worker implements Serializable {
return System.currentTimeMillis() < retryUntil;
}
private boolean needsForegroundService(@NonNull Data data) {
NetworkRequirement networkRequirement = new NetworkRequirement(getApplicationContext());
boolean requiresNetwork = data.getBoolean(KEY_REQUIRES_NETWORK, false);
return requiresNetwork && !networkRequirement.isPresent();
}
private void log(@NonNull String message) {
log(message, null);
}

View File

@ -41,6 +41,7 @@ public class JobManager {
Data.Builder dataBuilder = new Data.Builder().putInt(Job.KEY_RETRY_COUNT, jobParameters.getRetryCount())
.putLong(Job.KEY_RETRY_UNTIL, jobParameters.getRetryUntil())
.putLong(Job.KEY_SUBMIT_TIME, System.currentTimeMillis())
.putBoolean(Job.KEY_REQUIRES_NETWORK, jobParameters.requiresNetwork())
.putBoolean(Job.KEY_REQUIRES_MASTER_SECRET, jobParameters.requiresMasterSecret())
.putBoolean(Job.KEY_REQUIRES_SQLCIPHER, jobParameters.requiresSqlCipher());
Data data = job.serialize(dataBuilder);

View File

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.dependencies.InjectableType;
import org.thoughtcrime.securesms.jobmanager.JobParameters;
import org.thoughtcrime.securesms.jobmanager.SafeData;
@ -43,6 +44,11 @@ public class PushNotificationReceiveJob extends PushReceivedJob implements Injec
return dataBuilder.build();
}
@Override
protected String getDescription() {
return context.getString(R.string.PushNotificationReceiveJob_retrieving_a_message);
}
@Override
public void onRun() throws IOException {
receiver.retrieveMessages(new SignalServiceMessageReceiver.MessageReceivedCallback() {

View File

@ -4,6 +4,7 @@ import android.content.Context;
import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.TextSecureExpiredException;
import org.thoughtcrime.securesms.attachments.Attachment;
import org.thoughtcrime.securesms.crypto.MasterSecret;
@ -31,6 +32,11 @@ public abstract class SendJob extends MasterSecretJob {
super(context, parameters);
}
@Override
protected String getDescription() {
return context.getString(R.string.SendJob_sending_a_message);
}
@Override
public final void onRun(MasterSecret masterSecret) throws Exception {
if (Util.getDaysTillBuildExpiry() <= 0) {