Show a banner in the event of a service outage.

We will now determine if there has been a service outage and render a
banner at the top of the conversation list if we detect that there has
been one.
master
Greyson Parrelli 2018-06-11 09:37:01 -07:00
parent 0999359454
commit 2c17b54ef9
19 changed files with 190 additions and 11 deletions

View File

@ -270,6 +270,7 @@ android {
buildConfigField "long", "BUILD_TIMESTAMP", getLastCommitTimestamp() + "L"
buildConfigField "String", "SIGNAL_URL", "\"https://textsecure-service.whispersystems.org\""
buildConfigField "String", "SIGNAL_CDN_URL", "\"https://cdn.signal.org\""
buildConfigField "String", "SIGNAL_SERVICE_STATUS_URL", "\"uptime.signal.org\""
buildConfigField "String", "GIPHY_PROXY_HOST", "\"giphy-proxy-production.whispersystems.org\""
buildConfigField "int", "GIPHY_PROXY_PORT", "80"
buildConfigField "String", "USER_AGENT", "\"OWA\""

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" android:drawable="@color/core_red_highlight" />
<item android:state_focused="true" android:drawable="@color/core_red_highlight" />
<item android:drawable="@color/core_red" />
</selector>

View File

@ -5,7 +5,7 @@
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/reminder_background"
android:background="@drawable/reminder_background_normal"
android:focusable="true"
android:orientation="horizontal"
tools:visibility="visible">

View File

@ -5,7 +5,7 @@
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/reminder_background"
android:background="@drawable/reminder_background_normal"
android:focusable="true"
android:nextFocusRight="@+id/cancel"
android:orientation="horizontal"

View File

@ -5,7 +5,7 @@
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/reminder_background"
android:background="@drawable/reminder_background_normal"
android:focusable="true"
android:nextFocusRight="@+id/cancel"
android:orientation="horizontal"

View File

@ -58,4 +58,7 @@
<color name="transparent">#00FFFFFF</color>
<color name="MediaOverview_Media_selected_overlay">#88000000</color>
<color name="core_red">#f44336</color>
<color name="core_red_highlight">#ef5350</color>
</resources>

View File

@ -1361,6 +1361,7 @@
<string name="reminder_header_invite_text">Take your conversation with %1$s to the next level.</string>
<string name="reminder_header_share_title">Invite your friends!</string>
<string name="reminder_header_share_text">The more friends use Signal, the better it gets.</string>
<string name="reminder_header_service_outage_text">Signal is experiencing technical difficulties. We are working hard to restore service as quickly as possible.</string>
<!-- media_preview -->
<string name="media_preview__save_title">Save</string>

View File

@ -94,6 +94,7 @@ import org.thoughtcrime.securesms.components.location.SignalPlace;
import org.thoughtcrime.securesms.components.reminder.ExpiredBuildReminder;
import org.thoughtcrime.securesms.components.reminder.InviteReminder;
import org.thoughtcrime.securesms.components.reminder.ReminderView;
import org.thoughtcrime.securesms.components.reminder.ServiceOutageReminder;
import org.thoughtcrime.securesms.components.reminder.UnauthorizedReminder;
import org.thoughtcrime.securesms.contacts.ContactAccessor;
import org.thoughtcrime.securesms.contacts.ContactAccessor.ContactData;
@ -121,6 +122,7 @@ import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
import org.thoughtcrime.securesms.giph.ui.GiphyActivity;
import org.thoughtcrime.securesms.jobs.MultiDeviceBlockedUpdateJob;
import org.thoughtcrime.securesms.jobs.RetrieveProfileJob;
import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob;
import org.thoughtcrime.securesms.mms.AttachmentManager;
import org.thoughtcrime.securesms.mms.AttachmentManager.MediaType;
import org.thoughtcrime.securesms.mms.AudioSlide;
@ -1115,12 +1117,15 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
}
protected void updateReminders(boolean seenInvite) {
Log.w(TAG, "updateReminders(" + seenInvite+")");
Log.w(TAG, "updateReminders(" + seenInvite + ")");
if (UnauthorizedReminder.isEligible(this)) {
reminderView.get().showReminder(new UnauthorizedReminder(this));
} else if (ExpiredBuildReminder.isEligible()) {
reminderView.get().showReminder(new ExpiredBuildReminder(this));
} else if (ServiceOutageReminder.isEligible(this)) {
ApplicationContext.getInstance(this).getJobManager().add(new ServiceOutageDetectionJob(this));
reminderView.get().showReminder(new ServiceOutageReminder(this));
} else if (TextSecurePreferences.isPushRegistered(this) &&
TextSecurePreferences.isShowInviteReminders(this) &&
!isSecureText &&

View File

@ -66,14 +66,15 @@ import org.thoughtcrime.securesms.components.reminder.OutdatedBuildReminder;
import org.thoughtcrime.securesms.components.reminder.PushRegistrationReminder;
import org.thoughtcrime.securesms.components.reminder.Reminder;
import org.thoughtcrime.securesms.components.reminder.ReminderView;
import org.thoughtcrime.securesms.components.reminder.ServiceOutageReminder;
import org.thoughtcrime.securesms.components.reminder.ShareReminder;
import org.thoughtcrime.securesms.components.reminder.SystemSmsImportReminder;
import org.thoughtcrime.securesms.components.reminder.UnauthorizedReminder;
import org.thoughtcrime.securesms.crypto.MasterSecret;
import org.thoughtcrime.securesms.database.DatabaseFactory;
import org.thoughtcrime.securesms.database.MessagingDatabase.MarkedMessageInfo;
import org.thoughtcrime.securesms.database.loaders.ConversationListLoader;
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
import org.thoughtcrime.securesms.jobs.ServiceOutageDetectionJob;
import org.thoughtcrime.securesms.mms.GlideApp;
import org.thoughtcrime.securesms.notifications.MarkReadReceiver;
import org.thoughtcrime.securesms.notifications.MessageNotifier;
@ -81,6 +82,7 @@ import org.thoughtcrime.securesms.recipients.Recipient;
import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
import org.thoughtcrime.securesms.util.task.SnackbarAsyncTask;
import org.whispersystems.jobqueue.JobManager;
import org.whispersystems.libsignal.util.guava.Optional;
import java.util.HashSet;
@ -190,6 +192,9 @@ public class ConversationListFragment extends Fragment
return Optional.of(new UnauthorizedReminder(context));
} else if (ExpiredBuildReminder.isEligible()) {
return Optional.of(new ExpiredBuildReminder(context));
} else if (ServiceOutageReminder.isEligible(context)) {
ApplicationContext.getInstance(context).getJobManager().add(new ServiceOutageDetectionJob(context));
return Optional.of(new ServiceOutageReminder(context));
} else if (OutdatedBuildReminder.isEligible()) {
return Optional.of(new OutdatedBuildReminder(context));
} else if (DefaultSmsReminder.isEligible(context)) {

View File

@ -1,6 +1,7 @@
package org.thoughtcrime.securesms.components.reminder;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.View.OnClickListener;
public abstract class Reminder {
@ -10,14 +11,14 @@ public abstract class Reminder {
private OnClickListener okListener;
private OnClickListener dismissListener;
public Reminder(@NonNull CharSequence title,
@NonNull CharSequence text)
public Reminder(@Nullable CharSequence title,
@NonNull CharSequence text)
{
this.title = title;
this.text = text;
this.title = title;
this.text = text;
}
public CharSequence getTitle() {
public @Nullable CharSequence getTitle() {
return title;
}
@ -44,4 +45,13 @@ public abstract class Reminder {
public boolean isDismissable() {
return true;
}
public @NonNull Importance getImportance() {
return Importance.NORMAL;
}
public enum Importance {
NORMAL, ERROR
}
}

View File

@ -3,6 +3,7 @@ package org.thoughtcrime.securesms.components.reminder;
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build.VERSION_CODES;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@ -49,8 +50,16 @@ public class ReminderView extends LinearLayout {
}
public void showReminder(final Reminder reminder) {
title.setText(reminder.getTitle());
if (!TextUtils.isEmpty(reminder.getTitle())) {
title.setText(reminder.getTitle());
title.setVisibility(VISIBLE);
} else {
title.setText("");
title.setVisibility(GONE);
}
text.setText(reminder.getText());
container.setBackgroundResource(reminder.getImportance() == Reminder.Importance.ERROR ? R.drawable.reminder_background_error
: R.drawable.reminder_background_normal);
setOnClickListener(reminder.getOkListener());

View File

@ -0,0 +1,30 @@
package org.thoughtcrime.securesms.components.reminder;
import android.content.Context;
import android.support.annotation.NonNull;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
public class ServiceOutageReminder extends Reminder {
public ServiceOutageReminder(@NonNull Context context) {
super(null,
context.getString(R.string.reminder_header_service_outage_text));
}
public static boolean isEligible(@NonNull Context context) {
return TextSecurePreferences.getServiceOutage(context);
}
@Override
public boolean isDismissable() {
return false;
}
@NonNull
@Override
public Importance getImportance() {
return Importance.ERROR;
}
}

View File

@ -139,6 +139,8 @@ public class PushGroupSendJob extends PushSendJob implements InjectableType {
@Override
public void onCanceled() {
super.onCanceled();
DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageId);
}

View File

@ -94,6 +94,8 @@ public class PushMediaSendJob extends PushSendJob implements InjectableType {
@Override
public void onCanceled() {
super.onCanceled();
DatabaseFactory.getMmsDatabase(context).markAsSentFailed(messageId);
notifyMediaMessageDeliveryFailed(context, messageId);
}

View File

@ -71,6 +71,11 @@ public abstract class PushSendJob extends SendJob {
onPushSend();
}
@Override
public void onCanceled() {
ApplicationContext.getInstance(context).getJobManager().add(new ServiceOutageDetectionJob(context));
}
protected Optional<byte[]> getProfileKey(@NonNull Recipient recipient) {
if (!recipient.resolve().isSystemContact() && !recipient.resolve().isProfileSharing()) {
return Optional.absent();

View File

@ -83,6 +83,8 @@ public class PushTextSendJob extends PushSendJob implements InjectableType {
@Override
public void onCanceled() {
super.onCanceled();
DatabaseFactory.getSmsDatabase(context).markAsSentFailed(messageId);
long threadId = DatabaseFactory.getSmsDatabase(context).getThreadIdForMessage(messageId);

View File

@ -0,0 +1,78 @@
package org.thoughtcrime.securesms.jobs;
import android.content.Context;
import android.util.Log;
import org.greenrobot.eventbus.EventBus;
import org.thoughtcrime.securesms.BuildConfig;
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
import org.thoughtcrime.securesms.transport.RetryLaterException;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.whispersystems.jobqueue.JobParameters;
import org.whispersystems.jobqueue.requirements.NetworkRequirement;
import java.net.InetAddress;
import java.net.UnknownHostException;
public class ServiceOutageDetectionJob extends ContextJob {
private static final String TAG = ServiceOutageDetectionJob.class.getSimpleName();
private static final String IP_SUCCESS = "127.0.0.1";
private static final String IP_FAILURE = "127.0.0.2";
private static final long CHECK_TIME = 1000 * 60;
public ServiceOutageDetectionJob(Context context) {
super(context, new JobParameters.Builder()
.withGroupId(ServiceOutageDetectionJob.class.getSimpleName())
.withRequirement(new NetworkRequirement(context))
.withRetryCount(5)
.create());
}
@Override
public void onAdded() {
}
@Override
public void onRun() throws RetryLaterException {
long timeSinceLastCheck = System.currentTimeMillis() - TextSecurePreferences.getLastOutageCheckTime(context);
if (timeSinceLastCheck < CHECK_TIME) {
Log.w(TAG, "Skipping service outage check. Too soon.");
return;
}
try {
InetAddress address = InetAddress.getByName(BuildConfig.SIGNAL_SERVICE_STATUS_URL);
if (IP_SUCCESS.equals(address.getHostAddress())) {
Log.w(TAG, "Service is available.");
TextSecurePreferences.setServiceOutage(context, false);
} else if (IP_FAILURE.equals(address.getHostAddress())) {
Log.w(TAG, "Service is down.");
TextSecurePreferences.setServiceOutage(context, true);
} else {
Log.w(TAG, "Service status check returned an unrecognized IP address. Assuming outage.");
TextSecurePreferences.setServiceOutage(context, true);
}
TextSecurePreferences.setLastOutageCheckTime(context, System.currentTimeMillis());
EventBus.getDefault().post(new ReminderUpdateEvent());
} catch (UnknownHostException e) {
throw new RetryLaterException(e);
}
}
@Override
public boolean onShouldRetry(Exception e) {
return e instanceof RetryLaterException;
}
@Override
public void onCanceled() {
Log.w(TAG, "Service status check could not complete. Assuming success to avoid false positives due to bad network.");
TextSecurePreferences.setServiceOutage(context, false);
TextSecurePreferences.setLastOutageCheckTime(context, System.currentTimeMillis());
EventBus.getDefault().post(new ReminderUpdateEvent());
}
}

View File

@ -15,6 +15,7 @@ import android.util.Log;
import org.greenrobot.eventbus.EventBus;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.events.ReminderUpdateEvent;
import org.thoughtcrime.securesms.jobs.requirements.SqlCipherMigrationRequirementProvider;
import org.thoughtcrime.securesms.lock.RegistrationLockReminders;
import org.thoughtcrime.securesms.preferences.widgets.NotificationPrivacyPreference;
@ -150,6 +151,9 @@ public class TextSecurePreferences {
private static final String REGISTRATION_LOCK_LAST_REMINDER_TIME = "pref_registration_lock_last_reminder_time";
private static final String REGISTRATION_LOCK_NEXT_REMINDER_INTERVAL = "pref_registration_lock_next_reminder_interval";
private static final String SERVICE_OUTAGE = "pref_service_outage";
private static final String LAST_OUTAGE_CHECK_TIME = "pref_last_outage_check_time";
public static boolean isScreenLockEnabled(@NonNull Context context) {
return getBooleanPreference(context, SCREEN_LOCK, false);
}
@ -903,6 +907,22 @@ public class TextSecurePreferences {
new HashSet<>(Arrays.asList(context.getResources().getStringArray(defaultValuesRes))));
}
public static void setLastOutageCheckTime(Context context, long timestamp) {
setLongPreference(context, LAST_OUTAGE_CHECK_TIME, timestamp);
}
public static long getLastOutageCheckTime(Context context) {
return getLongPreference(context, LAST_OUTAGE_CHECK_TIME, 0);
}
public static void setServiceOutage(Context context, boolean isOutage) {
setBooleanPreference(context, SERVICE_OUTAGE, isOutage);
}
public static boolean getServiceOutage(Context context) {
return getBooleanPreference(context, SERVICE_OUTAGE, false);
}
public static void setBooleanPreference(Context context, String key, boolean value) {
PreferenceManager.getDefaultSharedPreferences(context).edit().putBoolean(key, value).apply();
}