diff --git a/app/build.gradle b/app/build.gradle index 090e340828..1de3eee0d6 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -15,7 +15,7 @@ configurations.configureEach { exclude module: "commons-logging" } -def canonicalVersionCode = 395 +def canonicalVersionCode = 396 def canonicalVersionName = "1.21.0" def postFixSize = 10 diff --git a/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigUploader.kt b/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigUploader.kt index 59bd20825b..b3ac243416 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigUploader.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/configs/ConfigUploader.kt @@ -44,7 +44,7 @@ import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.Namespace import org.session.libsignal.utilities.Snode import org.session.libsignal.utilities.retryWithUniformInterval -import org.thoughtcrime.securesms.util.InternetConnectivity +import org.thoughtcrime.securesms.util.NetworkConnectivity import javax.inject.Inject private const val TAG = "ConfigUploader" @@ -64,7 +64,7 @@ class ConfigUploader @Inject constructor( private val configFactory: ConfigFactoryProtocol, private val storageProtocol: StorageProtocol, private val clock: SnodeClock, - private val internetConnectivity: InternetConnectivity, + private val networkConnectivity: NetworkConnectivity, private val textSecurePreferences: TextSecurePreferences, ) { private var job: Job? = null @@ -77,7 +77,7 @@ class ConfigUploader @Inject constructor( * The value pushed doesn't matter as nothing is emitted when the conditions are not met. */ @OptIn(ExperimentalCoroutinesApi::class) - private fun pathBecomesAvailable(): Flow<*> = internetConnectivity.networkAvailable + private fun pathBecomesAvailable(): Flow<*> = networkConnectivity.networkAvailable .flatMapLatest { hasNetwork -> if (hasNetwork) { OnionRequestAPI.hasPath.filter { it } diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupPollerManager.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupPollerManager.kt index 1780622e3c..cd58bc11a2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/GroupPollerManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/GroupPollerManager.kt @@ -1,6 +1,5 @@ package org.thoughtcrime.securesms.groups -import dagger.Lazy import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -12,6 +11,7 @@ import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.first @@ -24,8 +24,6 @@ import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.scan import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.supervisorScope -import org.session.libsession.database.StorageProtocol -import org.session.libsession.messaging.groups.GroupManagerV2 import org.session.libsession.snode.SnodeClock import org.session.libsession.utilities.ConfigUpdateNotification import org.session.libsession.utilities.TextSecurePreferences @@ -34,7 +32,7 @@ import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.dependencies.ConfigFactory import org.thoughtcrime.securesms.util.AppVisibilityManager -import org.thoughtcrime.securesms.util.InternetConnectivity +import org.thoughtcrime.securesms.util.NetworkConnectivity import javax.inject.Inject import javax.inject.Singleton @@ -58,15 +56,18 @@ class GroupPollerManager @Inject constructor( clock: SnodeClock, preferences: TextSecurePreferences, appVisibilityManager: AppVisibilityManager, - connectivity: InternetConnectivity, + connectivity: NetworkConnectivity, groupRevokedMessageHandler: GroupRevokedMessageHandler, ) { @Suppress("OPT_IN_USAGE") private val groupPollers: StateFlow> = combine( - connectivity.networkAvailable, + connectivity.networkAvailable.debounce(200L), preferences.watchLocalNumber() - ) { networkAvailable, localNumber -> networkAvailable && localNumber != null } + ) { networkAvailable, localNumber -> + Log.v(TAG, "Network available: $networkAvailable, hasLocalNumber: ${localNumber != null}") + networkAvailable && localNumber != null + } // This flatMap produces a flow of groups that should be polled now .flatMapLatest { shouldPoll -> if (shouldPoll) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/logging/LogFile.java b/app/src/main/java/org/thoughtcrime/securesms/logging/LogFile.java index cfe6cc3809..2072e783b2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/logging/LogFile.java +++ b/app/src/main/java/org/thoughtcrime/securesms/logging/LogFile.java @@ -129,25 +129,37 @@ String readAll() throws IOException { String readEntry() throws IOException { try { + // Read the IV and length Util.readFully(inputStream, ivBuffer); Util.readFully(inputStream, intBuffer); + } catch (EOFException e) { + // End of file reached before a full header could be read. + return null; + } - int length = Conversions.byteArrayToInt(intBuffer); - byte[] ciphertext = ciphertextBuffer.get(length); + int length = Conversions.byteArrayToInt(intBuffer); + byte[] ciphertext = ciphertextBuffer.get(length); + try { Util.readFully(inputStream, ciphertext, length); + } catch (EOFException e) { + // Incomplete ciphertext – likely due to a partially written record. + return null; + } - try { - synchronized (CIPHER_LOCK) { - cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(secret, "AES"), new IvParameterSpec(ivBuffer)); - byte[] plaintext = cipher.doFinal(ciphertext, 0, length); - return new String(plaintext); - } - } catch (InvalidKeyException | InvalidAlgorithmParameterException | IllegalBlockSizeException | BadPaddingException e) { - throw new AssertionError(e); + try { + synchronized (CIPHER_LOCK) { + cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(secret, "AES"), new IvParameterSpec(ivBuffer)); + byte[] plaintext = cipher.doFinal(ciphertext, 0, length); + return new String(plaintext); } - } catch (EOFException e) { + } catch (BadPaddingException e) { + // Bad padding likely indicates a corrupted or incomplete entry. + // Instead of throwing an error, treat this as the end of the log. return null; + } catch (InvalidKeyException | InvalidAlgorithmParameterException + | IllegalBlockSizeException e) { + throw new AssertionError(e); } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt index 03dd82141b..b3e1ee3fd2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsActivity.kt @@ -2,10 +2,8 @@ import android.Manifest import android.app.Activity -import android.content.BroadcastReceiver import android.content.Context import android.content.Intent -import android.content.IntentFilter import android.graphics.BitmapFactory import android.net.Uri import android.os.Bundle @@ -53,18 +51,10 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.lifecycle.lifecycleScope -import androidx.localbroadcastmanager.content.LocalBroadcastManager import com.canhub.cropper.CropImageContract import com.squareup.phrase.Phrase import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.channels.awaitClose -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import network.loki.messenger.BuildConfig import network.loki.messenger.R import network.loki.messenger.databinding.ActivitySettingsBinding @@ -103,7 +93,6 @@ import org.thoughtcrime.securesms.ui.theme.PreviewTheme import org.thoughtcrime.securesms.ui.theme.SessionColorsParameterProvider import org.thoughtcrime.securesms.ui.theme.ThemeColors import org.thoughtcrime.securesms.ui.theme.dangerButtonColors -import org.thoughtcrime.securesms.util.InternetConnectivity import org.thoughtcrime.securesms.util.push import java.io.File import javax.inject.Inject diff --git a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt index 20e52b1eac..676905313e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/preferences/SettingsViewModel.kt @@ -36,7 +36,7 @@ import org.thoughtcrime.securesms.preferences.SettingsViewModel.AvatarDialogStat import org.thoughtcrime.securesms.profiles.ProfileMediaConstraints import org.thoughtcrime.securesms.util.BitmapDecodingException import org.thoughtcrime.securesms.util.BitmapUtil -import org.thoughtcrime.securesms.util.InternetConnectivity +import org.thoughtcrime.securesms.util.NetworkConnectivity import java.io.File import java.io.IOException import javax.inject.Inject @@ -46,7 +46,7 @@ class SettingsViewModel @Inject constructor( @ApplicationContext private val context: Context, private val prefs: TextSecurePreferences, private val configFactory: ConfigFactory, - private val connectivity: InternetConnectivity, + private val connectivity: NetworkConnectivity, ) : ViewModel() { private val TAG = "SettingsViewModel" diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt index 8ae08c9c47..6f55dde70e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/service/WebRtcCallService.kt @@ -43,7 +43,7 @@ import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_IN import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_INCOMING_RINGING import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.TYPE_OUTGOING_RINGING import org.thoughtcrime.securesms.util.CallNotificationBuilder.Companion.WEBRTC_NOTIFICATION -import org.thoughtcrime.securesms.util.InternetConnectivity +import org.thoughtcrime.securesms.util.NetworkConnectivity import org.thoughtcrime.securesms.webrtc.AudioManagerCommand import org.thoughtcrime.securesms.webrtc.CallManager import org.thoughtcrime.securesms.webrtc.CallViewModel @@ -219,7 +219,7 @@ class WebRtcCallService : LifecycleService(), CallManager.WebRtcListener { lateinit var callManager: CallManager @Inject - lateinit var internetConnectivity: InternetConnectivity + lateinit var networkConnectivity: NetworkConnectivity private var wantsToAnswer = false private var currentTimeouts = 0 @@ -335,7 +335,7 @@ class WebRtcCallService : LifecycleService(), CallManager.WebRtcListener { registerUncaughtExceptionHandler() GlobalScope.launch { - internetConnectivity.networkAvailable.collectLatest(::networkChange) + networkConnectivity.networkAvailable.collectLatest(::networkChange) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/InternetConnectivity.kt b/app/src/main/java/org/thoughtcrime/securesms/util/NetworkConnectivity.kt similarity index 53% rename from app/src/main/java/org/thoughtcrime/securesms/util/InternetConnectivity.kt rename to app/src/main/java/org/thoughtcrime/securesms/util/NetworkConnectivity.kt index 7cdd382a9a..738f28f7ab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/InternetConnectivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/util/NetworkConnectivity.kt @@ -6,49 +6,41 @@ import android.content.Context.CONNECTIVITY_SERVICE import android.net.ConnectivityManager import android.net.ConnectivityManager.NetworkCallback import android.net.Network -import android.net.NetworkCapabilities -import android.net.NetworkRequest import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.callbackFlow -import kotlinx.coroutines.flow.distinctUntilChanged -import kotlinx.coroutines.flow.shareIn import kotlinx.coroutines.flow.stateIn +import org.session.libsignal.utilities.Log import javax.inject.Inject import javax.inject.Singleton /** - * Provides a flow that emits `true` when the device has internet connectivity and `false` otherwise. + * Provides a flow that emits `true` when the device has network connectivity. We won't be sure + * if there's internet or not, it's by designed so that we don't get false negatives in censorship + * countries. */ @Singleton -class InternetConnectivity @Inject constructor(application: Application) { +class NetworkConnectivity @Inject constructor(application: Application) { val networkAvailable = callbackFlow { val connectivityManager = application.getSystemService(ConnectivityManager::class.java) val callback = object : NetworkCallback() { - override fun onCapabilitiesChanged( - network: Network, - networkCapabilities: NetworkCapabilities - ) { - trySend(networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) - } - override fun onAvailable(network: Network) { + super.onAvailable(network) + + Log.v("NetworkConnectivity", "Network become available") trySend(true) } override fun onLost(network: Network) { + super.onLost(network) + Log.v("NetworkConnectivity", "Network become lost") trySend(false) } } - connectivityManager.registerNetworkCallback( - NetworkRequest.Builder() - .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - .build(), - callback - ) + connectivityManager.registerDefaultNetworkCallback(callback) awaitClose { connectivityManager.unregisterNetworkCallback(callback) @@ -65,15 +57,7 @@ class InternetConnectivity @Inject constructor(application: Application) { private fun haveValidNetworkConnection(context: Context): Boolean { val cm = context.getSystemService(CONNECTIVITY_SERVICE) as ConnectivityManager - // Early exit if we have no active network.. - val activeNetwork = cm.activeNetwork ?: return false - - // ..otherwise determine what capabilities are available to the active network. - val networkCapabilities = cm.getNetworkCapabilities(activeNetwork) - val internetConnectionValid = networkCapabilities != null && - networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) - - return internetConnectionValid + return cm.activeNetwork != null } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/groups/LegacyGroupDeprecationManager.kt b/libsession/src/main/java/org/session/libsession/messaging/groups/LegacyGroupDeprecationManager.kt index bdfeec6012..e8c8635dc8 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/groups/LegacyGroupDeprecationManager.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/groups/LegacyGroupDeprecationManager.kt @@ -24,7 +24,7 @@ class LegacyGroupDeprecationManager(private val prefs: TextSecurePreferences) { // The time all legacy groups will cease working. This value can be overridden by a debug // facility. - private val defaultDeprecatedTime = ZonedDateTime.of(2025, 7, 1, 0, 0, 0, 0, ZoneId.of("UTC")) + private val defaultDeprecatedTime = ZonedDateTime.of(2025, 4, 2, 22, 0, 0, 0, ZoneId.of("UTC")) private val mutableDeprecatedTime = MutableStateFlow( prefs.deprecatedTimeOverride ?: defaultDeprecatedTime @@ -33,7 +33,7 @@ class LegacyGroupDeprecationManager(private val prefs: TextSecurePreferences) { val deprecatedTime: StateFlow get() = mutableDeprecatedTime // The time a warning will be shown to users that legacy groups are being deprecated. - private val defaultDeprecatingStartTime = ZonedDateTime.of(2025, 2, 24, 0, 0, 0, 0, ZoneId.of("UTC")) + private val defaultDeprecatingStartTime = ZonedDateTime.of(2025, 3, 19, 22, 0, 0, 0, ZoneId.of("UTC")) private val mutableDeprecatingStartTime: MutableStateFlow = MutableStateFlow( prefs.deprecatingStartTimeOverride ?: defaultDeprecatingStartTime