Show megaphone to improve network reliability.

This commit is contained in:
Greyson Parrelli
2022-03-04 13:30:22 -05:00
committed by Alex Hart
parent 427e73f7fd
commit 852dcd9711
10 changed files with 217 additions and 4 deletions

View File

@@ -47,6 +47,7 @@ import org.thoughtcrime.securesms.dependencies.ApplicationDependencyProvider;
import org.thoughtcrime.securesms.emoji.EmojiSource;
import org.thoughtcrime.securesms.emoji.JumboEmoji;
import org.thoughtcrime.securesms.gcm.FcmJobService;
import org.thoughtcrime.securesms.jobs.CheckServiceReachabilityJob;
import org.thoughtcrime.securesms.jobs.CreateSignedPreKeyJob;
import org.thoughtcrime.securesms.jobs.DownloadLatestEmojiDataJob;
import org.thoughtcrime.securesms.jobs.EmojiSearchIndexDownloadJob;
@@ -199,6 +200,7 @@ public class ApplicationContext extends MultiDexApplication implements AppForegr
.addPostRender(RetrieveReleaseChannelJob::enqueue)
.addPostRender(() -> AndroidTelecomUtil.registerPhoneAccount())
.addPostRender(() -> ApplicationDependencies.getJobManager().add(new FontDownloaderJob()))
.addPostRender(CheckServiceReachabilityJob::enqueueIfNecessary)
.execute();
Log.d(TAG, "onCreate() took " + (System.currentTimeMillis() - startTime) + " ms");

View File

@@ -77,6 +77,7 @@ class AdvancedPrivacySettingsViewModel(
fun setCensorshipCircumventionEnabled(enabled: Boolean) {
SignalStore.settings().setCensorshipCircumventionEnabled(enabled)
SignalStore.misc().isServiceReachableWithoutCircumvention = false
ApplicationDependencies.resetNetworkConnectionsAfterProxyChange()
refresh()
}

View File

@@ -0,0 +1,119 @@
package org.thoughtcrime.securesms.jobs
import org.signal.core.util.logging.Log
import org.thoughtcrime.securesms.BuildConfig
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies
import org.thoughtcrime.securesms.jobmanager.Data
import org.thoughtcrime.securesms.jobmanager.Job
import org.thoughtcrime.securesms.jobmanager.impl.NetworkConstraint
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.whispersystems.libsignal.util.guava.Optional
import org.whispersystems.signalservice.api.websocket.WebSocketConnectionState
import org.whispersystems.signalservice.internal.util.StaticCredentialsProvider
import org.whispersystems.signalservice.internal.websocket.WebSocketConnection
import java.util.concurrent.TimeUnit
/**
* Checks to see if a censored user can establish a websocket connection with an uncensored network configuration.
*/
class CheckServiceReachabilityJob private constructor(params: Parameters) : BaseJob(params) {
constructor() : this(
Parameters.Builder()
.addConstraint(NetworkConstraint.KEY)
.setLifespan(TimeUnit.HOURS.toMillis(12))
.setMaxAttempts(1)
.build()
)
companion object {
private val TAG = Log.tag(CheckServiceReachabilityJob::class.java)
const val KEY = "CheckServiceReachabilityJob"
@JvmStatic
fun enqueueIfNecessary() {
val isCensored = ApplicationDependencies.getSignalServiceNetworkAccess().isCensored()
val timeSinceLastCheck = System.currentTimeMillis() - SignalStore.misc().lastCensorshipServiceReachabilityCheckTime
if (SignalStore.account().isRegistered && isCensored && timeSinceLastCheck > TimeUnit.DAYS.toMillis(1)) {
ApplicationDependencies.getJobManager().add(CheckServiceReachabilityJob())
}
}
}
override fun serialize(): Data {
return Data.EMPTY
}
override fun getFactoryKey(): String {
return KEY
}
override fun onRun() {
if (!SignalStore.account().isRegistered) {
Log.w(TAG, "Not registered, skipping.")
SignalStore.misc().lastCensorshipServiceReachabilityCheckTime = System.currentTimeMillis()
return
}
if (!ApplicationDependencies.getSignalServiceNetworkAccess().isCensored()) {
Log.w(TAG, "Not currently censored, skipping.")
SignalStore.misc().lastCensorshipServiceReachabilityCheckTime = System.currentTimeMillis()
return
}
SignalStore.misc().lastCensorshipServiceReachabilityCheckTime = System.currentTimeMillis()
val uncensoredWebsocket = WebSocketConnection(
"uncensored-test",
ApplicationDependencies.getSignalServiceNetworkAccess().uncensoredConfiguration,
Optional.of(
StaticCredentialsProvider(
SignalStore.account().aci,
SignalStore.account().pni,
SignalStore.account().e164,
SignalStore.account().deviceId,
SignalStore.account().servicePassword
)
),
BuildConfig.SIGNAL_AGENT,
null,
""
)
try {
val startTime = System.currentTimeMillis()
val state: WebSocketConnectionState = uncensoredWebsocket.connect()
.filter { it == WebSocketConnectionState.CONNECTED || it == WebSocketConnectionState.FAILED }
.timeout(30, TimeUnit.SECONDS)
.blockingFirst(WebSocketConnectionState.FAILED)
if (state == WebSocketConnectionState.CONNECTED) {
Log.i(TAG, "Established connection in ${System.currentTimeMillis() - startTime} ms! Service is reachable!")
SignalStore.misc().isServiceReachableWithoutCircumvention = true
} else {
Log.w(TAG, "Failed to establish a connection in ${System.currentTimeMillis() - startTime} ms.")
SignalStore.misc().isServiceReachableWithoutCircumvention = false
}
} catch (exception: Exception) {
Log.w(TAG, "Failed to connect to the websocket.", exception)
SignalStore.misc().isServiceReachableWithoutCircumvention = false
} finally {
uncensoredWebsocket.disconnect()
}
}
override fun onShouldRetry(e: Exception): Boolean {
return false
}
override fun onFailure() {
}
class Factory : Job.Factory<CheckServiceReachabilityJob> {
override fun create(parameters: Parameters, data: Data): CheckServiceReachabilityJob {
return CheckServiceReachabilityJob(parameters)
}
}
}

View File

@@ -82,6 +82,7 @@ public final class JobManagerFactories {
put(AvatarGroupsV1DownloadJob.KEY, new AvatarGroupsV1DownloadJob.Factory());
put(AvatarGroupsV2DownloadJob.KEY, new AvatarGroupsV2DownloadJob.Factory());
put(BoostReceiptRequestResponseJob.KEY, new BoostReceiptRequestResponseJob.Factory());
put(CheckServiceReachabilityJob.KEY, new CheckServiceReachabilityJob.Factory());
put(CleanPreKeysJob.KEY, new CleanPreKeysJob.Factory());
put(ClearFallbackKbsEnclaveJob.KEY, new ClearFallbackKbsEnclaveJob.Factory());
put(ConversationShortcutUpdateJob.KEY, new ConversationShortcutUpdateJob.Factory());

View File

@@ -16,6 +16,8 @@ public final class MiscellaneousValues extends SignalStoreValues {
private static final String OLD_DEVICE_TRANSFER_LOCKED = "misc.old_device.transfer.locked";
private static final String HAS_EVER_HAD_AN_AVATAR = "misc.has.ever.had.an.avatar";
private static final String CHANGE_NUMBER_LOCK = "misc.change_number.lock";
private static final String CENSORSHIP_LAST_CHECK_TIME = "misc.censorship.last_check_time";
private static final String CENSORSHIP_SERVICE_REACHABLE = "misc.censorship.service_reachable";
MiscellaneousValues(@NonNull KeyValueStore store) {
super(store);
@@ -110,4 +112,20 @@ public final class MiscellaneousValues extends SignalStoreValues {
public void unlockChangeNumber() {
putBoolean(CHANGE_NUMBER_LOCK, false);
}
public long getLastCensorshipServiceReachabilityCheckTime() {
return getLong(CENSORSHIP_LAST_CHECK_TIME, 0);
}
public void setLastCensorshipServiceReachabilityCheckTime(long value) {
putLong(CENSORSHIP_LAST_CHECK_TIME, value);
}
public boolean isServiceReachableWithoutCircumvention() {
return getBoolean(CENSORSHIP_SERVICE_REACHABLE, false);
}
public void setServiceReachableWithoutCircumvention(boolean value) {
putBoolean(CENSORSHIP_SERVICE_REACHABLE, value);
}
}

View File

@@ -106,6 +106,7 @@ public final class Megaphones {
put(Event.CLIENT_DEPRECATED, SignalStore.misc().isClientDeprecated() ? ALWAYS : NEVER);
put(Event.NOTIFICATIONS, shouldShowNotificationsMegaphone(context) ? RecurringSchedule.every(TimeUnit.DAYS.toMillis(30)) : NEVER);
put(Event.ONBOARDING, shouldShowOnboardingMegaphone(context) ? ALWAYS : NEVER);
put(Event.TURN_OFF_CENSORSHIP_CIRCUMVENTION, shouldShowTurnOffCircumventionMegaphone() ? RecurringSchedule.every(TimeUnit.DAYS.toMillis(7)) : NEVER);
put(Event.BECOME_A_SUSTAINER, shouldShowDonateMegaphone(context, records) ? ShowForDurationSchedule.showForDays(7) : NEVER);
put(Event.PIN_REMINDER, new SignalPinReminderSchedule());
@@ -136,6 +137,8 @@ public final class Megaphones {
return buildBecomeASustainerMegaphone(context);
case NOTIFICATION_PROFILES:
return buildNotificationProfilesMegaphone(context);
case TURN_OFF_CENSORSHIP_CIRCUMVENTION:
return buildTurnOffCircumventionMegaphone(context);
default:
throw new IllegalArgumentException("Event not handled!");
}
@@ -295,6 +298,21 @@ public final class Megaphones {
.build();
}
private static @NonNull Megaphone buildTurnOffCircumventionMegaphone(@NonNull Context context) {
return new Megaphone.Builder(Event.TURN_OFF_CENSORSHIP_CIRCUMVENTION, Megaphone.Style.BASIC)
.setTitle(R.string.CensorshipCircumventionMegaphone_turn_off_censorship_circumvention)
.setImage(R.drawable.ic_censorship_megaphone_64)
.setBody(R.string.CensorshipCircumventionMegaphone_you_can_now_connect_to_the_signal_service)
.setActionButton(R.string.CensorshipCircumventionMegaphone_turn_off, (megaphone, listener) -> {
SignalStore.settings().setCensorshipCircumventionEnabled(false);
listener.onMegaphoneSnooze(Event.TURN_OFF_CENSORSHIP_CIRCUMVENTION);
})
.setSecondaryButton(R.string.CensorshipCircumventionMegaphone_no_thanks, (megaphone, listener) -> {
listener.onMegaphoneSnooze(Event.TURN_OFF_CENSORSHIP_CIRCUMVENTION);
})
.build();
}
private static boolean shouldShowDonateMegaphone(@NonNull Context context, @NonNull Map<Event, MegaphoneRecord> records) {
long timeSinceLastDonatePrompt = timeSinceLastDonatePrompt(records);
@@ -313,6 +331,11 @@ public final class Megaphones {
return SignalStore.onboarding().hasOnboarding(context);
}
private static boolean shouldShowTurnOffCircumventionMegaphone() {
return ApplicationDependencies.getSignalServiceNetworkAccess().isCensored() &&
SignalStore.misc().isServiceReachableWithoutCircumvention();
}
private static boolean shouldShowNotificationsMegaphone(@NonNull Context context) {
boolean shouldShow = !SignalStore.settings().isMessageNotificationsEnabled() ||
!NotificationChannels.isMessageChannelEnabled(context) ||
@@ -374,7 +397,8 @@ public final class Megaphones {
ADD_A_PROFILE_PHOTO("add_a_profile_photo"),
BECOME_A_SUSTAINER("become_a_sustainer"),
VALENTINES_DONATIONS_2022("valentines_donations_2022"),
NOTIFICATION_PROFILES("notification_profiles");
NOTIFICATION_PROFILES("notification_profiles"),
TURN_OFF_CENSORSHIP_CIRCUMVENTION("turn_off_censorship_circumvention");
private final String key;

View File

@@ -1,8 +1,6 @@
package org.thoughtcrime.securesms.megaphone;
import org.thoughtcrime.securesms.dependencies.ApplicationDependencies;
import org.thoughtcrime.securesms.keyvalue.SignalStore;
import org.thoughtcrime.securesms.util.TextSecurePreferences;
final class SignalPinReminderSchedule implements MegaphoneSchedule {

View File

@@ -211,7 +211,7 @@ class SignalServiceNetworkAccess(context: Context) {
COUNTRY_CODE_UZBEKISTAN,
)
private val uncensoredConfiguration: SignalServiceConfiguration = SignalServiceConfiguration(
val uncensoredConfiguration: SignalServiceConfiguration = SignalServiceConfiguration(
arrayOf(SignalServiceUrl(BuildConfig.SIGNAL_URL, serviceTrustStore)),
mapOf(
0 to arrayOf(SignalCdnUrl(BuildConfig.SIGNAL_CDN_URL, serviceTrustStore)),