mirror of
https://github.com/signalapp/Signal-Android.git
synced 2026-02-23 03:05:26 +00:00
Show megaphone to improve network reliability.
This commit is contained in:
committed by
Alex Hart
parent
427e73f7fd
commit
852dcd9711
@@ -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");
|
||||
|
||||
@@ -77,6 +77,7 @@ class AdvancedPrivacySettingsViewModel(
|
||||
|
||||
fun setCensorshipCircumventionEnabled(enabled: Boolean) {
|
||||
SignalStore.settings().setCensorshipCircumventionEnabled(enabled)
|
||||
SignalStore.misc().isServiceReachableWithoutCircumvention = false
|
||||
ApplicationDependencies.resetNetworkConnectionsAfterProxyChange()
|
||||
refresh()
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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)),
|
||||
|
||||
Reference in New Issue
Block a user