From 51635cadad95f978913c50905ba1df005457a4ed Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Wed, 12 Jun 2024 21:55:31 +0530 Subject: [PATCH 001/188] ui: new setting to enable/disable crashlytics in play version --- .../java/com/celzero/bravedns/util/Logger.kt | 8 +++ .../res/layout/activity_misc_settings.xml | 56 +++++++++++++++++++ .../bravedns/service/PersistentState.kt | 2 + app/src/main/res/drawable/ic_firebase.xml | 9 +++ app/src/main/res/values/strings.xml | 3 + .../java/com/celzero/bravedns/util/Logger.kt | 12 ++++ .../java/com/celzero/bravedns/util/Logger.kt | 8 +++ 7 files changed, 98 insertions(+) create mode 100644 app/src/main/res/drawable/ic_firebase.xml diff --git a/app/src/fdroid/java/com/celzero/bravedns/util/Logger.kt b/app/src/fdroid/java/com/celzero/bravedns/util/Logger.kt index fa043e17c..c4c54c07e 100644 --- a/app/src/fdroid/java/com/celzero/bravedns/util/Logger.kt +++ b/app/src/fdroid/java/com/celzero/bravedns/util/Logger.kt @@ -105,6 +105,14 @@ object Logger : KoinComponent { log(tag, message, LoggerType.ERROR, e) } + fun enableCrashlytics() { + // no-op, crashlytics is not used for F-Droid builds + } + + fun disableCrashlytics() { + // no-op, crashlytics is not used for F-Droid builds + } + fun updateConfigLevel(level: Long) { logLevel = level } diff --git a/app/src/full/res/layout/activity_misc_settings.xml b/app/src/full/res/layout/activity_misc_settings.xml index ad7650629..b465ed94a 100644 --- a/app/src/full/res/layout/activity_misc_settings.xml +++ b/app/src/full/res/layout/activity_misc_settings.xml @@ -693,6 +693,62 @@ + + + + + + + + + + + + + + (true) + var crashlyticsEnabled by booleanPref("crashlytics_enabled").withDefault(Utilities.isPlayStoreFlavour()) + var orbotConnectionStatus: MutableLiveData = MutableLiveData() var median: MutableLiveData = MutableLiveData() var vpnEnabledLiveData: MutableLiveData = MutableLiveData() diff --git a/app/src/main/res/drawable/ic_firebase.xml b/app/src/main/res/drawable/ic_firebase.xml new file mode 100644 index 000000000..eb80dcbd4 --- /dev/null +++ b/app/src/main/res/drawable/ic_firebase.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bf1e177dc..f20ac7939 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -628,6 +628,9 @@ Error creating pcap file, Try again! + Stablility improvement program + When app encounters an error, error logs are obtained and analysed to produce a solution. + Application Icon diff --git a/app/src/play/java/com/celzero/bravedns/util/Logger.kt b/app/src/play/java/com/celzero/bravedns/util/Logger.kt index 88cd79abc..ec7b82c9f 100644 --- a/app/src/play/java/com/celzero/bravedns/util/Logger.kt +++ b/app/src/play/java/com/celzero/bravedns/util/Logger.kt @@ -119,6 +119,18 @@ object Logger : KoinComponent { } } + fun enableCrashlytics() { + if (Utilities.isPlayStoreFlavour()) { + FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true) + } + } + + fun disableCrashlytics() { + if (Utilities.isPlayStoreFlavour()) { + FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(false) + } + } + fun updateConfigLevel(level: Long) { logLevel = level } diff --git a/app/src/website/java/com/celzero/bravedns/util/Logger.kt b/app/src/website/java/com/celzero/bravedns/util/Logger.kt index d687590aa..fc00cf73d 100644 --- a/app/src/website/java/com/celzero/bravedns/util/Logger.kt +++ b/app/src/website/java/com/celzero/bravedns/util/Logger.kt @@ -106,6 +106,14 @@ object Logger : KoinComponent { log(tag, message, LoggerType.ERROR, e) } + fun enableCrashlytics() { + // no-op, Crashlytics is not used for website variant + } + + fun disableCrashlytics() { + // no-op, Crashlytics is not used for website variant + } + fun updateConfigLevel(level: Long) { logLevel = level } From 9188e52d788379e27ccae06c2e0b1e2ffbcebb64 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Wed, 12 Jun 2024 21:57:56 +0530 Subject: [PATCH 002/188] fix: #1529, better handling of wg states in ui --- .../bravedns/adapter/OneWgConfigAdapter.kt | 167 +++++++++++++----- .../bravedns/adapter/WgConfigAdapter.kt | 132 +++++++++++--- .../bravedns/service/WireguardManager.kt | 42 +++-- .../ui/activity/MiscSettingsActivity.kt | 26 +++ .../ui/activity/WgConfigDetailActivity.kt | 80 +++++++-- 5 files changed, 353 insertions(+), 94 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt index b01e08ab1..7f8316930 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt @@ -15,6 +15,7 @@ */ package com.celzero.bravedns.adapter +import Logger.LOG_TAG_PROXY import android.content.Context import android.content.Intent import android.text.format.DateUtils @@ -37,6 +38,10 @@ import com.celzero.bravedns.databinding.ListItemWgOneInterfaceBinding import com.celzero.bravedns.service.ProxyManager import com.celzero.bravedns.service.VpnController import com.celzero.bravedns.service.WireguardManager +import com.celzero.bravedns.service.WireguardManager.ERR_CODE_OTHER_WG_ACTIVE +import com.celzero.bravedns.service.WireguardManager.ERR_CODE_VPN_NOT_ACTIVE +import com.celzero.bravedns.service.WireguardManager.ERR_CODE_VPN_NOT_FULL +import com.celzero.bravedns.service.WireguardManager.ERR_CODE_WG_INVALID import com.celzero.bravedns.ui.activity.WgConfigDetailActivity import com.celzero.bravedns.ui.activity.WgConfigDetailActivity.Companion.INTENT_EXTRA_WG_TYPE import com.celzero.bravedns.ui.activity.WgConfigEditorActivity.Companion.INTENT_EXTRA_WG_ID @@ -105,27 +110,23 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns fun update(config: WgConfigFiles) { b.interfaceNameText.text = config.name - b.oneWgCheck.isChecked = config.isActive - io { - updateStatus(config) - } + val isWgActive = config.isActive && VpnController.hasTunnel() + b.oneWgCheck.isChecked = isWgActive setupClickListeners(config) - if (config.oneWireGuard) { + if (isWgActive) { keepStatusUpdated(config) } else { - b.interfaceDetailCard.strokeWidth = 0 - b.interfaceAppsCount.visibility = View.GONE - b.protocolInfoChipGroup.visibility = View.GONE - b.interfaceActiveLayout.visibility = View.GONE - b.oneWgCheck.isChecked = false - b.interfaceStatus.text = - context.getString(R.string.lbl_disabled).replaceFirstChar(Char::titlecase) + disableInterface() } } private fun keepStatusUpdated(config: WgConfigFiles) { + if (statusCheckJob?.isActive == true) return + statusCheckJob = io { - while (true) { + for (i in 0 until 10) { + if (statusCheckJob?.isActive == false) return@io + updateStatus(config) delay(ONE_SEC) } @@ -172,6 +173,11 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns return } + if (config.isActive && !VpnController.hasTunnel()) { + disableInterface() + return + } + val id = ProxyManager.ID_WG_BASE + config.id val statusId = VpnController.getProxyStatusById(id) val pair = VpnController.getSupportedIpVersion(id) @@ -191,7 +197,7 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns } private fun updateStatusUi(config: WgConfigFiles, statusId: Long?, stats: Stats?) { - if (config.isActive) { + if (config.isActive && VpnController.hasTunnel()) { b.interfaceDetailCard.strokeWidth = 2 b.oneWgCheck.isChecked = true b.interfaceAppsCount.visibility = View.VISIBLE @@ -210,7 +216,10 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns b.interfaceDetailCard.strokeColor = fetchColor(context, R.attr.accentGood) } - } else if (statusId == Backend.TUP || statusId == Backend.TZZ) { + } else if (statusId == Backend.TUP || + statusId == Backend.TZZ || + statusId == Backend.TNT + ) { b.interfaceDetailCard.strokeColor = fetchColor(context, R.attr.chipTextNeutral) } else { @@ -265,15 +274,21 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns } b.interfaceActiveRxTx.text = rxtx } else { - b.interfaceDetailCard.strokeWidth = 0 - b.interfaceAppsCount.visibility = View.GONE - b.oneWgCheck.isChecked = false - b.interfaceActiveLayout.visibility = View.GONE - b.interfaceStatus.text = - context.getString(R.string.lbl_disabled).replaceFirstChar(Char::titlecase) + disableInterface() } } + private fun disableInterface() { + statusCheckJob?.cancel() + b.interfaceDetailCard.strokeWidth = 0 + b.protocolInfoChipGroup.visibility = View.GONE + b.interfaceAppsCount.visibility = View.GONE + b.oneWgCheck.isChecked = false + b.interfaceActiveLayout.visibility = View.GONE + b.interfaceStatus.text = + context.getString(R.string.lbl_disabled).replaceFirstChar(Char::titlecase) + } + private fun getUpTime(stats: Stats?): CharSequence { if (stats == null) { return "" @@ -327,34 +342,100 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns val isChecked = b.oneWgCheck.isChecked io { if (isChecked) { - if (WireguardManager.canEnableConfig(config.toImmutable())) { - config.oneWireGuard = true - WireguardManager.updateOneWireGuardConfig(config.id, owg = true) - WireguardManager.enableConfig(config.toImmutable()) - uiCtx { listener.onDnsStatusChanged() } - } else { - uiCtx { - b.oneWgCheck.isChecked = false - Utilities.showToastUiCentered( - context, - context.getString(R.string.wireguard_enabled_failure), - Toast.LENGTH_LONG - ) - } - } + enableWgIfPossible(config) } else { - config.oneWireGuard = false - WireguardManager.updateOneWireGuardConfig(config.id, owg = false) - WireguardManager.disableConfig(config.toImmutable()) - uiCtx { - b.oneWgCheck.isChecked = false - listener.onDnsStatusChanged() - } + disableWgIfPossible(config) } } } } + private suspend fun enableWgIfPossible(config: WgConfigFiles) { + if (!VpnController.hasTunnel()) { + Logger.i(LOG_TAG_PROXY, "VPN not active, cannot enable WireGuard") + uiCtx { + Utilities.showToastUiCentered( + context, + ERR_CODE_VPN_NOT_ACTIVE + context.getString(R.string.settings_socks5_vpn_disabled_error), + Toast.LENGTH_LONG + ) + // reset the check box + b.oneWgCheck.isChecked = false + } + return + } + + if (!WireguardManager.canEnableProxy()) { + Logger.i(LOG_TAG_PROXY, "not in DNS+Firewall mode, cannot enable WireGuard") + uiCtx { + // reset the check box + b.oneWgCheck.isChecked = false + Utilities.showToastUiCentered( + context, + ERR_CODE_VPN_NOT_FULL + context.getString(R.string.wireguard_enabled_failure), + Toast.LENGTH_LONG + ) + } + return + } + + if (WireguardManager.isAnyOtherOneWgEnabled(config.id)) { + Logger.i(LOG_TAG_PROXY, "another WireGuard is already enabled") + uiCtx { + // reset the check box + b.oneWgCheck.isChecked = false + Utilities.showToastUiCentered( + context, + ERR_CODE_OTHER_WG_ACTIVE + context.getString(R.string.wireguard_enabled_failure), + Toast.LENGTH_LONG + ) + } + return + } + + if (!WireguardManager.isValidConfig(config.id)) { + Logger.i(LOG_TAG_PROXY, "invalid WireGuard config") + uiCtx { + // reset the check box + b.oneWgCheck.isChecked = false + Utilities.showToastUiCentered( + context, + ERR_CODE_WG_INVALID + context.getString(R.string.wireguard_enabled_failure), + Toast.LENGTH_LONG + ) + } + return + } + + Logger.i(LOG_TAG_PROXY, "enabling WireGuard, id: ${config.id}") + WireguardManager.updateOneWireGuardConfig(config.id, owg = true) + config.oneWireGuard = true + WireguardManager.enableConfig(config.toImmutable()) + uiCtx { listener.onDnsStatusChanged() } + } + + private suspend fun disableWgIfPossible(config: WgConfigFiles) { + if (!VpnController.hasTunnel()) { + Logger.i(LOG_TAG_PROXY, "VPN not active, cannot disable WireGuard") + uiCtx { + Utilities.showToastUiCentered( + context, + ERR_CODE_VPN_NOT_ACTIVE + context.getString(R.string.settings_socks5_vpn_disabled_error), + Toast.LENGTH_LONG + ) + // reset the check box + b.oneWgCheck.isChecked = true + } + return + } + + Logger.i(LOG_TAG_PROXY, "disabling WireGuard, id: ${config.id}") + WireguardManager.updateOneWireGuardConfig(config.id, owg = false) + config.oneWireGuard = false + WireguardManager.disableConfig(config.toImmutable()) + uiCtx { listener.onDnsStatusChanged() } + } + private fun launchConfigDetail(id: Int) { val intent = Intent(context, WgConfigDetailActivity::class.java) intent.putExtra(INTENT_EXTRA_WG_ID, id) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt index 48c019a84..2db0b6bd7 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt @@ -15,6 +15,7 @@ */ package com.celzero.bravedns.adapter +import Logger.LOG_TAG_PROXY import android.content.Context import android.content.Intent import android.text.format.DateUtils @@ -32,10 +33,15 @@ import backend.Backend import backend.Stats import com.celzero.bravedns.R import com.celzero.bravedns.database.WgConfigFiles +import com.celzero.bravedns.database.WgConfigFilesImmutable import com.celzero.bravedns.databinding.ListItemWgGeneralInterfaceBinding import com.celzero.bravedns.service.ProxyManager import com.celzero.bravedns.service.VpnController import com.celzero.bravedns.service.WireguardManager +import com.celzero.bravedns.service.WireguardManager.ERR_CODE_OTHER_WG_ACTIVE +import com.celzero.bravedns.service.WireguardManager.ERR_CODE_VPN_NOT_ACTIVE +import com.celzero.bravedns.service.WireguardManager.ERR_CODE_VPN_NOT_FULL +import com.celzero.bravedns.service.WireguardManager.ERR_CODE_WG_INVALID import com.celzero.bravedns.ui.activity.WgConfigDetailActivity import com.celzero.bravedns.ui.activity.WgConfigEditorActivity.Companion.INTENT_EXTRA_WG_ID import com.celzero.bravedns.util.UIUtils @@ -106,13 +112,13 @@ class WgConfigAdapter(private val context: Context) : fun update(config: WgConfigFiles) { b.interfaceNameText.text = config.name - b.interfaceSwitch.isChecked = config.isActive + b.interfaceSwitch.isChecked = config.isActive && VpnController.hasTunnel() setupClickListeners(config) updateStatusJob(config) } private fun updateStatusJob(config: WgConfigFiles) { - if (config.isActive) { + if (config.isActive && VpnController.hasTunnel()) { val job = updateProxyStatusContinuously(config) if (job != null) { // cancel the job if it already exists for the same config @@ -323,7 +329,7 @@ class WgConfigAdapter(private val context: Context) : UIUtils.fetchColor(context, R.attr.chipTextNeutral) } else { b.interfaceDetailCard.strokeColor = - UIUtils.fetchColor(context, R.attr.accentBad) + UIUtils.fetchColor(context, R.attr.chipTextNegative) } status = if (stats?.lastOK == 0L) { @@ -421,32 +427,112 @@ class WgConfigAdapter(private val context: Context) : b.interfaceSwitch.setOnCheckedChangeListener(null) b.interfaceSwitch.setOnClickListener { val cfg = config.toImmutable() - if (b.interfaceSwitch.isChecked) { - if (WireguardManager.canEnableConfig(cfg)) { - WireguardManager.enableConfig(cfg) + io { + if (b.interfaceSwitch.isChecked) { + enableWgIfPossible(cfg) } else { - Utilities.showToastUiCentered( - context, - context.getString(R.string.wireguard_enabled_failure), - Toast.LENGTH_LONG - ) - b.interfaceSwitch.isChecked = false - } - } else { - if (WireguardManager.canDisableConfig(cfg)) { - WireguardManager.disableConfig(cfg) - } else { - Utilities.showToastUiCentered( - context, - context.getString(R.string.wireguard_disable_failure), - Toast.LENGTH_LONG - ) - b.interfaceSwitch.isChecked = true + disableWgIfPossible(cfg) } } } } + private suspend fun disableWgIfPossible(cfg: WgConfigFilesImmutable) { + if (!VpnController.hasTunnel()) { + Logger.i(LOG_TAG_PROXY, "VPN not active, cannot enable WireGuard") + uiCtx { + Utilities.showToastUiCentered( + context, + ERR_CODE_VPN_NOT_ACTIVE + context.getString(R.string.settings_socks5_vpn_disabled_error), + Toast.LENGTH_LONG + ) + // reset the check box + b.interfaceSwitch.isChecked = true + } + return + } + + if (WireguardManager.canDisableConfig(cfg)) { + WireguardManager.disableConfig(cfg) + } else { + Utilities.showToastUiCentered( + context, + context.getString(R.string.wireguard_disable_failure), + Toast.LENGTH_LONG + ) + b.interfaceSwitch.isChecked = true + } + + WireguardManager.disableConfig(cfg) + } + + private suspend fun enableWgIfPossible(cfg: WgConfigFilesImmutable) { + + if (!VpnController.hasTunnel()) { + Logger.i(LOG_TAG_PROXY, "VPN not active, cannot enable WireGuard") + uiCtx { + Utilities.showToastUiCentered( + context, + ERR_CODE_VPN_NOT_ACTIVE + context.getString(R.string.settings_socks5_vpn_disabled_error), + Toast.LENGTH_LONG + ) + // reset the check box + b.interfaceSwitch.isChecked = false + } + return + } + + if (!WireguardManager.canEnableProxy()) { + Logger.i( + LOG_TAG_PROXY, + "not in DNS+Firewall mode, cannot enable WireGuard" + ) + uiCtx { + // reset the check box + b.interfaceSwitch.isChecked = false + Utilities.showToastUiCentered( + context, + ERR_CODE_VPN_NOT_FULL + context.getString(R.string.wireguard_enabled_failure), + Toast.LENGTH_LONG + ) + } + return + } + + if (WireguardManager.oneWireGuardEnabled()) { + // this should not happen, ui is disabled if one wireGuard is enabled + Logger.w(LOG_TAG_PROXY, "one wireGuard is already enabled") + uiCtx { + // reset the check box + b.interfaceSwitch.isChecked = false + Utilities.showToastUiCentered( + context, + ERR_CODE_OTHER_WG_ACTIVE + context.getString( + R.string.wireguard_enabled_failure + ), + Toast.LENGTH_LONG + ) + } + return + } + + if (!WireguardManager.isValidConfig(cfg.id)) { + Logger.i(LOG_TAG_PROXY, "invalid WireGuard config") + uiCtx { + // reset the check box + b.interfaceSwitch.isChecked = false + Utilities.showToastUiCentered( + context, + ERR_CODE_WG_INVALID + context.getString(R.string.wireguard_enabled_failure), + Toast.LENGTH_LONG + ) + } + return + } + + WireguardManager.enableConfig(cfg) + } + private fun launchConfigDetail(id: Int) { val intent = Intent(context, WgConfigDetailActivity::class.java) intent.putExtra(INTENT_EXTRA_WG_ID, id) diff --git a/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt b/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt index f56e40b91..bb4ace2eb 100644 --- a/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt +++ b/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt @@ -78,6 +78,12 @@ object WireguardManager : KoinComponent { const val WARP_ID = 1 const val WARP_FILE_NAME = "wg1.conf" + // let the error code be a string, so that it can be concatenated with the error message + const val ERR_CODE_VPN_NOT_ACTIVE = "1" + const val ERR_CODE_VPN_NOT_FULL = "2" + const val ERR_CODE_OTHER_WG_ACTIVE = "3" + const val ERR_CODE_WG_INVALID = "4" + // invalid config id const val INVALID_CONF_ID = -1 @@ -229,23 +235,31 @@ object WireguardManager : KoinComponent { return } - fun canEnableConfig(map: WgConfigFilesImmutable): Boolean { - val canEnable = appConfig.canEnableProxy() && appConfig.canEnableWireguardProxy() - if (!canEnable) { - return false - } - // if one wireguard is enabled, don't allow to enable another - if (oneWireGuardEnabled()) { - return false - } - val config = configs.find { it.getId() == map.id } + fun canEnableProxy(): Boolean { + return appConfig.canEnableProxy() + } + + fun isValidConfig(id: Int): Boolean { + val config = configs.find { it.getId() == id } if (config == null) { - Logger.e(LOG_TAG_PROXY, "canEnableConfig: wg not found, id: ${map.id}, ${configs.size}") + Logger.e(LOG_TAG_PROXY, "canEnableConfig: wg not found, id: ${id}, ${configs.size}") return false } return true } + fun isAnyOtherOneWgEnabled(id: Int): Boolean { + return mappings.any { it.oneWireGuard && it.isActive && it.id != id } + } + + fun canEnableWg(): Boolean { + val canEnableProxy = appConfig.canEnableProxy() + val canEnableWireGuardProxy = appConfig.canEnableWireguardProxy() + val canEnable = canEnableProxy && canEnableWireGuardProxy + Logger.i(LOG_TAG_PROXY, "canEnableConfig? $canEnableProxy && $canEnableWireGuardProxy") + return canEnable + } + fun canDisableConfig(map: WgConfigFilesImmutable): Boolean { // do not allow to disable the proxy if it is catch-all return !map.isCatchAll @@ -435,15 +449,15 @@ object WireguardManager : KoinComponent { // there maybe catch-all config enabled, so return the active catch-all config val catchAllConfig = mappings.find { it.isActive && it.isCatchAll } return if (catchAllConfig == null) { - Logger.d(LOG_TAG_PROXY, "catch all config not found for uid: $uid") + Logger.i(LOG_TAG_PROXY, "catch all config not found for uid: $uid") null } else { val optimalId = fetchOptimalCatchAllConfig(uid, ip) if (optimalId == null) { - Logger.d(LOG_TAG_PROXY, "no catch all config found for uid: $uid") + Logger.i(LOG_TAG_PROXY, "no catch all config found for uid: $uid") null } else { - Logger.d(LOG_TAG_PROXY, "catch all config found for uid: $uid, $optimalId") + Logger.i(LOG_TAG_PROXY, "catch all config found for uid: $uid, $optimalId") mappings.find { it.id == optimalId } } } diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/MiscSettingsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/MiscSettingsActivity.kt index 69d4f7585..eac3b17ef 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/MiscSettingsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/MiscSettingsActivity.kt @@ -64,6 +64,7 @@ import com.celzero.bravedns.util.Utilities.delay import com.celzero.bravedns.util.Utilities.isAtleastR import com.celzero.bravedns.util.Utilities.isAtleastT import com.celzero.bravedns.util.Utilities.isFdroidFlavour +import com.celzero.bravedns.util.Utilities.isPlayStoreFlavour import com.celzero.bravedns.util.Utilities.showToastUiCentered import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.koin.android.ext.android.inject @@ -109,6 +110,13 @@ class MiscSettingsActivity : AppCompatActivity(R.layout.activity_misc_settings) b.settingsActivityAutoStartSwitch.isChecked = persistentState.prefAutoStartBootUp // check for app updates b.settingsActivityCheckUpdateSwitch.isChecked = persistentState.checkForAppUpdate + // crashlytics + if (isPlayStoreFlavour()) { + b.settingsCrashlyticsRl.visibility = View.VISIBLE + b.settingsActivityCrashlyticsSwitch.isChecked = persistentState.crashlyticsEnabled + } else { + b.settingsCrashlyticsRl.visibility = View.GONE + } // for app locale (default system/user selected locale) if (isAtleastT()) { @@ -307,6 +315,24 @@ class MiscSettingsActivity : AppCompatActivity(R.layout.activity_misc_settings) // Reset the biometric auth time persistentState.biometricAuthTime = Constants.INIT_TIME_MS } + + b.settingsCrashlyticsRl.setOnClickListener { + b.settingsActivityCrashlyticsSwitch.isChecked = + !b.settingsActivityCrashlyticsSwitch.isChecked + } + + b.settingsActivityCrashlyticsSwitch.setOnCheckedChangeListener { _: CompoundButton, b: Boolean + -> + // only for play store version + persistentState.crashlyticsEnabled = b + if (b) { + // enable crashlytics + Logger.enableCrashlytics() + } else { + // disable crashlytics + Logger.disableCrashlytics() + } + } } private fun invokeChangeLocaleDialog() { diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt index ee88283a4..4df8762e0 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt @@ -28,12 +28,18 @@ import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import by.kirich1409.viewbindingdelegate.viewBinding import com.celzero.bravedns.R +import com.celzero.bravedns.adapter.WgConfigAdapter import com.celzero.bravedns.adapter.WgIncludeAppsAdapter import com.celzero.bravedns.adapter.WgPeersAdapter import com.celzero.bravedns.databinding.ActivityWgDetailBinding import com.celzero.bravedns.service.PersistentState import com.celzero.bravedns.service.ProxyManager +import com.celzero.bravedns.service.VpnController import com.celzero.bravedns.service.WireguardManager +import com.celzero.bravedns.service.WireguardManager.ERR_CODE_OTHER_WG_ACTIVE +import com.celzero.bravedns.service.WireguardManager.ERR_CODE_VPN_NOT_ACTIVE +import com.celzero.bravedns.service.WireguardManager.ERR_CODE_VPN_NOT_FULL +import com.celzero.bravedns.service.WireguardManager.ERR_CODE_WG_INVALID import com.celzero.bravedns.service.WireguardManager.INVALID_CONF_ID import com.celzero.bravedns.ui.dialog.WgAddPeerDialog import com.celzero.bravedns.ui.dialog.WgIncludeAppsDialog @@ -300,6 +306,7 @@ class WgConfigDetailActivity : AppCompatActivity(R.layout.activity_wg_detail) { b.peersList.visibility = View.VISIBLE } */ + private fun handleAppsCount() { val id = ProxyManager.ID_WG_BASE + configId b.applicationsBtn.isEnabled = true @@ -377,33 +384,78 @@ class WgConfigDetailActivity : AppCompatActivity(R.layout.activity_wg_detail) { private fun updateCatchAll(enabled: Boolean) { io { - val config = WireguardManager.getConfigFilesById(configId) - if (config == null) { - Logger.e(LOG_TAG_PROXY, "updateCatchAll: config not found for $configId") + if (!VpnController.hasTunnel()) { + uiCtx { + Utilities.showToastUiCentered( + this, + ERR_CODE_VPN_NOT_ACTIVE + getString(R.string.settings_socks5_vpn_disabled_error), + Toast.LENGTH_LONG + ) + b.catchAllCheck.isChecked = !enabled + } return@io } - if (WireguardManager.canEnableConfig(config)) { - WireguardManager.updateCatchAllConfig(configId, enabled) + + if (!WireguardManager.canEnableProxy()) { + Logger.i( + LOG_TAG_PROXY, + "not in DNS+Firewall mode, cannot enable WireGuard" + ) uiCtx { - b.lockdownCheck.isEnabled = !enabled - b.applicationsBtn.isEnabled = !enabled - if (enabled) { - b.applicationsBtn.text = getString(R.string.routing_remaining_apps) - } else { - handleAppsCount() - } + // reset the check box + b.catchAllCheck.isChecked = false + Utilities.showToastUiCentered( + this, + ERR_CODE_VPN_NOT_FULL + getString(R.string.wireguard_enabled_failure), + Toast.LENGTH_LONG + ) } - } else { + return@io + } + + if (WireguardManager.oneWireGuardEnabled()) { + // this should not happen, ui is disabled if one wireGuard is enabled + Logger.w(LOG_TAG_PROXY, "one wireGuard is already enabled") uiCtx { + // reset the check box + b.catchAllCheck.isChecked = false Utilities.showToastUiCentered( this, - getString(R.string.wireguard_enabled_failure), + ERR_CODE_OTHER_WG_ACTIVE + getString( + R.string.wireguard_enabled_failure + ), Toast.LENGTH_LONG ) + } + return@io + } + + + val config = WireguardManager.getConfigFilesById(configId) + if (config == null) { + Logger.e(LOG_TAG_PROXY, "updateCatchAll: config not found for $configId") + uiCtx { + // reset the check box b.catchAllCheck.isChecked = false + Utilities.showToastUiCentered( + this, + ERR_CODE_WG_INVALID + getString(R.string.wireguard_enabled_failure), + Toast.LENGTH_LONG + ) } return@io } + + WireguardManager.updateCatchAllConfig(configId, enabled) + uiCtx { + b.lockdownCheck.isEnabled = !enabled + b.applicationsBtn.isEnabled = !enabled + if (enabled) { + b.applicationsBtn.text = getString(R.string.routing_remaining_apps) + } else { + handleAppsCount() + } + } } } From 41a7f3fd07cc9d125d62fde7ea7b16d46faaf248 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Fri, 14 Jun 2024 21:13:18 +0530 Subject: [PATCH 003/188] tunnel: stop adapter on other vpn start by user --- .../receiver/NotificationActionReceiver.kt | 2 +- .../ui/fragment/HomeScreenFragment.kt | 2 +- .../celzero/bravedns/backup/BackupHelper.kt | 2 +- .../bravedns/service/BraveTileService.kt | 2 +- .../bravedns/service/BraveVPNService.kt | 45 ++++++++++++------- .../celzero/bravedns/service/VpnController.kt | 4 +- 6 files changed, 36 insertions(+), 21 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/receiver/NotificationActionReceiver.kt b/app/src/full/java/com/celzero/bravedns/receiver/NotificationActionReceiver.kt index 97b411b04..feae06b0e 100644 --- a/app/src/full/java/com/celzero/bravedns/receiver/NotificationActionReceiver.kt +++ b/app/src/full/java/com/celzero/bravedns/receiver/NotificationActionReceiver.kt @@ -95,7 +95,7 @@ class NotificationActionReceiver : BroadcastReceiver(), KoinComponent { } private fun stopVpn(context: Context) { - VpnController.stop(context) + VpnController.stop("notif", context) } private fun pauseApp(context: Context) { diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt index 909a047b2..fa8b769bd 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt @@ -1138,7 +1138,7 @@ class HomeScreenFragment : Fragment(R.layout.fragment_home_screen) { } private fun stopVpnService() { - VpnController.stop(requireContext()) + VpnController.stop("home", requireContext()) } private fun startVpnService() { diff --git a/app/src/main/java/com/celzero/bravedns/backup/BackupHelper.kt b/app/src/main/java/com/celzero/bravedns/backup/BackupHelper.kt index ebf38bf11..852bc47f6 100644 --- a/app/src/main/java/com/celzero/bravedns/backup/BackupHelper.kt +++ b/app/src/main/java/com/celzero/bravedns/backup/BackupHelper.kt @@ -98,7 +98,7 @@ class BackupHelper { fun stopVpn(context: Context) { Logger.i(Logger.LOG_TAG_BACKUP_RESTORE, "calling vpn stop from backup helper") - VpnController.stop(context) + VpnController.stop("bkup", context) } fun startVpn(context: Context) { diff --git a/app/src/main/java/com/celzero/bravedns/service/BraveTileService.kt b/app/src/main/java/com/celzero/bravedns/service/BraveTileService.kt index 1fec5169d..3d3128fdc 100644 --- a/app/src/main/java/com/celzero/bravedns/service/BraveTileService.kt +++ b/app/src/main/java/com/celzero/bravedns/service/BraveTileService.kt @@ -78,7 +78,7 @@ class BraveTileService : TileService(), KoinComponent { val isAppLockEnabled = persistentState.biometricAuth // do not start or stop VPN if app lock is enabled if (VpnController.state().activationRequested && !isAppLockEnabled) { - VpnController.stop(this) + VpnController.stop("tile",this) } else if (VpnService.prepare(this) == null && !isAppLockEnabled) { // Start VPN service when VPN permission has been granted VpnController.start(this) diff --git a/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt b/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt index 8b562658a..d4d09e826 100644 --- a/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt +++ b/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt @@ -1437,19 +1437,19 @@ class BraveVPNService : if (isAtleastU()) { var ok = startForegroundService(FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED) if (!ok) { - Logger.i(LOG_TAG_VPN, "start service failed, retrying with connected device") + Logger.w(LOG_TAG_VPN, "start service failed, retrying with connected device") ok = startForegroundService(FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE) } if (!ok) { - Logger.i(LOG_TAG_VPN, "start service failed, stopping service") - signalStopService(userInitiated = false) // notify and stop + Logger.w(LOG_TAG_VPN, "start service failed, stopping service") + signalStopService("startFg1", userInitiated = false) // notify and stop return@ui } } else { val ok = startForegroundService() if (!ok) { - Logger.i(LOG_TAG_VPN, "start service failed ( > U ), stopping service") - signalStopService(userInitiated = false) // notify and stop + Logger.w(LOG_TAG_VPN, "start service failed ( > U ), stopping service") + signalStopService("startFg2", userInitiated = false) // notify and stop return@ui } } @@ -1623,7 +1623,7 @@ class BraveVPNService : // conn-monitor and go-vpn-adapter exist, but persistent-state tracking vpn goes out // of sync Logger.e(LOG_TAG_VPN, "stop-vpn(updateTun), tracking vpn is out of sync") - io("outOfSync") { signalStopService(userInitiated = false) } + io("outOfSync") { signalStopService("outOfSync", userInitiated = false) } return } @@ -1633,7 +1633,7 @@ class BraveVPNService : // VpnController#onStartComplete if (ok == false) { Logger.w(LOG_TAG_VPN, "Cannot handle vpn adapter changes, no tunnel") - io("noTunnel") { signalStopService(userInitiated = false) } + io("noTunnel") { signalStopService("noTunnel", userInitiated = false) } return } notifyConnectionStateChangeIfNeeded() @@ -1923,11 +1923,10 @@ class BraveVPNService : vpnAdapter?.setDnsAlg() } - fun signalStopService(userInitiated: Boolean = true) { + fun signalStopService(reason: String, userInitiated: Boolean = true) { if (!userInitiated) notifyUserOnVpnFailure() - stopVpnAdapter() stopSelf() - Logger.i(LOG_TAG_VPN, "stopped vpn adapter and vpn service") + Logger.i(LOG_TAG_VPN, "stopped vpn adapter & service: $reason, $userInitiated") } private fun stopVpnAdapter() { @@ -1991,7 +1990,7 @@ class BraveVPNService : "$why, stop-vpn(restartVpn), tracking vpn is out of sync", Log.ERROR ) - io("outOfSyncRestart") { signalStopService(userInitiated = false) } + io("outOfSyncRestart") { signalStopService("outOfSyncRestart", userInitiated = false) } return } @@ -1999,14 +1998,14 @@ class BraveVPNService : val tunFd = establishVpn(networks) if (tunFd == null) { logAndToastIfNeeded("$why, cannot restart-vpn, no tun-fd", Log.ERROR) - io("noTunRestart") { signalStopService(userInitiated = false) } + io("noTunRestart") { signalStopService("noTunRestart1", userInitiated = false) } return } val ok = makeOrUpdateVpnAdapter(tunFd, opts, vpnProtos) // vpnProtos set in establishVpn() if (!ok) { logAndToastIfNeeded("$why, cannot restart-vpn, no vpn-adapter", Log.ERROR) - io("noTunnelRestart") { signalStopService(userInitiated = false) } + io("noTunnelRestart") { signalStopService("noTunRestart2", userInitiated = false) } return } else { logAndToastIfNeeded("$why, vpn restarted", Log.INFO) @@ -2101,7 +2100,7 @@ class BraveVPNService : override fun onNetworkRegistrationFailed() { Logger.i(LOG_TAG_VPN, "recd nw registration failed, stop vpn service with notification") - signalStopService(userInitiated = false) + signalStopService("nwRegFail", userInitiated = false) } override fun onNetworkConnected(networks: ConnectionMonitor.UnderlyingNetworks) { @@ -3118,16 +3117,21 @@ class BraveVPNService : override fun log(level: Int, msg: String) { val l = Logger.LoggerType.fromId(level) + writeConsoleLog(msg) if (l.stacktrace()) { Logger.crash(LOG_GO_LOGGER, msg) // log the stack trace EnhancedBugReport.writeLogsToFile(this, msg) } else if (l.user()) { showNwEngineNotification(msg) } else { - Logger.i(LOG_GO_LOGGER, msg) + // no-op } } + fun writeConsoleLog(msg: String) { + netLogTracker.writeConsoleLog(msg) + } + private fun showNwEngineNotification(msg: String) { if (msg.isEmpty()) { return @@ -3803,6 +3807,8 @@ class BraveVPNService : } override fun onTrimMemory(level: Int) { + // override onLowMemory is deprecated, so use onTrimMemory + // ref: developer.android.com/reference/android/net/VpnService super.onTrimMemory(level) Logger.i(LOG_TAG_VPN, "onTrimMemory: $level") if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) { @@ -3832,4 +3838,13 @@ class BraveVPNService : getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.notify(MEMORY_NOTIFICATION_ID, builder.build()) } + + override fun onUnbind(intent: Intent?): Boolean { + Logger.w(LOG_TAG_VPN, "onUnbind, stop vpn adapter") + // onUnbind is called when the vpn is disconnected by signalStopService or if + // some other vpn service is started by the user, so stop the vpn adapter in onUnbind which + // will close tunFd which is a prerequisite for onDestroy() + stopVpnAdapter() + return super.onUnbind(intent) + } } diff --git a/app/src/main/java/com/celzero/bravedns/service/VpnController.kt b/app/src/main/java/com/celzero/bravedns/service/VpnController.kt index c83cec5c6..2d855754e 100644 --- a/app/src/main/java/com/celzero/bravedns/service/VpnController.kt +++ b/app/src/main/java/com/celzero/bravedns/service/VpnController.kt @@ -139,11 +139,11 @@ object VpnController : KoinComponent { Logger.i(LOG_TAG_VPN, "VPNController - Start(Synchronized) executed - $context") } - fun stop(context: Context) { + fun stop(reason: String, context: Context) { Logger.i(LOG_TAG_VPN, "VPN Controller stop with context: $context") connectionState = null onConnectionStateChanged(connectionState) - braveVpnService?.signalStopService(userInitiated = true) + braveVpnService?.signalStopService(reason, userInitiated = true) } fun state(): VpnState { From 3423cca2a8a83554b2d0d998fb0ad8a91ccb89c7 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 15 Jun 2024 20:55:35 +0530 Subject: [PATCH 004/188] disable GWP-ASan for the app --- app/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 681df96af..72b2de263 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -37,6 +37,7 @@ android:label="@string/app_name" android:largeHeap="true" android:allowNativeHeapPointerTagging="false" + android:gwpAsanMode="never" android:memtagMode="off" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" From de43606f38fdbaa6d78303aab702ff028e37f1d0 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 24 Jun 2024 18:17:19 +0530 Subject: [PATCH 005/188] bump dependencies versions --- app/build.gradle | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index d230b3cea..c77ba4a8e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -175,7 +175,7 @@ configurations { dependencies { androidTestImplementation 'androidx.test:rules:1.5.0' def room_version = "2.6.1" - def paging_version = "3.2.1" + def paging_version = "3.3.0" implementation 'com.google.guava:guava:32.1.1-android' @@ -184,8 +184,8 @@ dependencies { coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4") fullImplementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.21' - fullImplementation 'androidx.appcompat:appcompat:1.6.1' - fullImplementation 'androidx.core:core-ktx:1.12.0' + fullImplementation 'androidx.appcompat:appcompat:1.7.0' + fullImplementation 'androidx.core:core-ktx:1.13.1' implementation 'androidx.preference:preference-ktx:1.2.1' fullImplementation 'androidx.constraintlayout:constraintlayout:2.1.4' fullImplementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' @@ -194,7 +194,7 @@ dependencies { fullImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3' // LiveData - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.7.0' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.2' implementation 'com.google.code.gson:gson:2.10.1' @@ -204,14 +204,14 @@ dependencies { implementation "androidx.room:room-paging:$room_version" fullImplementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' - fullImplementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0' - fullImplementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0' + fullImplementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.2' + fullImplementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.2' // Pagers Views implementation "androidx.paging:paging-runtime-ktx:$paging_version" - fullImplementation 'androidx.fragment:fragment-ktx:1.6.2' - implementation 'com.google.android.material:material:1.11.0' - fullImplementation 'androidx.viewpager2:viewpager2:1.0.0' + fullImplementation 'androidx.fragment:fragment-ktx:1.8.0' + implementation 'com.google.android.material:material:1.12.0' + fullImplementation 'androidx.viewpager2:viewpager2:1.1.0' fullImplementation 'com.squareup.okhttp3:okhttp:4.12.0' fullImplementation 'com.squareup.okhttp3:okhttp-dnsoverhttps:4.12.0' @@ -249,11 +249,11 @@ dependencies { fullImplementation 'com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.9' // from: https://jitpack.io/#celzero/firestack - download 'com.github.celzero:firestack:ee0a5ac71f@aar' - websiteImplementation 'com.github.celzero:firestack:ee0a5ac71f@aar' - fdroidImplementation 'com.github.celzero:firestack:ee0a5ac71f@aar' + download 'com.github.celzero:firestack:364ac37a66@aar' + websiteImplementation 'com.github.celzero:firestack:364ac37a66@aar' + fdroidImplementation 'com.github.celzero:firestack:364ac37a66@aar' // debug symbols for crashlytics - playImplementation 'com.github.celzero:firestack:ee0a5ac71f:debug@aar' + playImplementation 'com.github.celzero:firestack:364ac37a66:debug@aar' // Work manager implementation('androidx.work:work-runtime-ktx:2.9.0') { @@ -293,8 +293,8 @@ dependencies { // only using firebase crashlytics experimentally for stability tracking, only in play variant // not in fdroid or website - playImplementation 'com.google.firebase:firebase-crashlytics:19.0.0' - playImplementation 'com.google.firebase:firebase-crashlytics-ndk:19.0.0' + playImplementation 'com.google.firebase:firebase-crashlytics:19.0.1' + playImplementation 'com.google.firebase:firebase-crashlytics-ndk:19.0.1' } // github.com/michel-kraemer/gradle-download-task/issues/131#issuecomment-464476903 From d6ee645767acf9e79368557ab4c9f6be7cf83ca0 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 24 Jun 2024 20:23:47 +0530 Subject: [PATCH 006/188] logger: write console logs to an in-memory database --- .../java/com/celzero/bravedns/util/Logger.kt | 20 +++++++- .../celzero/bravedns/database/ConsoleLog.kt | 26 ++++++++++ .../bravedns/database/ConsoleLogDAO.kt | 49 +++++++++++++++++++ .../bravedns/database/ConsoleLogDatabase.kt | 37 ++++++++++++++ .../bravedns/database/ConsoleLogRepository.kt | 32 ++++++++++++ .../bravedns/database/DatabaseModule.kt | 4 ++ .../bravedns/service/ConsoleLogManager.kt | 36 ++++++++++++++ .../celzero/bravedns/service/NetLogTracker.kt | 32 +++++++++++- .../celzero/bravedns/service/ServiceModule.kt | 2 +- .../celzero/bravedns/service/VpnController.kt | 5 ++ .../java/com/celzero/bravedns/util/Logger.kt | 20 +++++++- .../java/com/celzero/bravedns/util/Logger.kt | 21 +++++++- 12 files changed, 277 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/com/celzero/bravedns/database/ConsoleLog.kt create mode 100644 app/src/main/java/com/celzero/bravedns/database/ConsoleLogDAO.kt create mode 100644 app/src/main/java/com/celzero/bravedns/database/ConsoleLogDatabase.kt create mode 100644 app/src/main/java/com/celzero/bravedns/database/ConsoleLogRepository.kt create mode 100644 app/src/main/java/com/celzero/bravedns/service/ConsoleLogManager.kt diff --git a/app/src/fdroid/java/com/celzero/bravedns/util/Logger.kt b/app/src/fdroid/java/com/celzero/bravedns/util/Logger.kt index c4c54c07e..9e5c108b7 100644 --- a/app/src/fdroid/java/com/celzero/bravedns/util/Logger.kt +++ b/app/src/fdroid/java/com/celzero/bravedns/util/Logger.kt @@ -15,6 +15,7 @@ */ import android.util.Log import com.celzero.bravedns.service.PersistentState +import com.celzero.bravedns.service.VpnController import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -40,7 +41,7 @@ object Logger : KoinComponent { const val LOG_GO_LOGGER = "LibLogger" // github.com/celzero/firestack/blob/bce8de917fec5e48a41ed1e96c9d942ee0f7996b/intra/log/logger.go#L76 - enum class LoggerType(val id: Int) { + enum class LoggerType(val id: Long) { VERY_VERBOSE(0), VERBOSE(1), DEBUG(2), @@ -72,6 +73,10 @@ object Logger : KoinComponent { return this == STACKTRACE } + fun isLessThan(level: LoggerType): Boolean { + return this.id < level.id + } + fun user(): Boolean { return this == USR } @@ -137,5 +142,18 @@ object Logger : KoinComponent { LoggerType.USR -> {} // Do nothing LoggerType.NONE -> {} // Do nothing } + if (type.id >= logLevel) { + // get the first letter of the level and append it to the tag + val l = when (type) { + LoggerType.VERBOSE -> "V" + LoggerType.DEBUG -> "D" + LoggerType.INFO -> "I" + LoggerType.WARN -> "W" + LoggerType.ERROR -> "E" + LoggerType.STACKTRACE -> "E" + else -> "V" + } + VpnController.writeConsoleLog("$l $tag: $msg") + } } } diff --git a/app/src/main/java/com/celzero/bravedns/database/ConsoleLog.kt b/app/src/main/java/com/celzero/bravedns/database/ConsoleLog.kt new file mode 100644 index 000000000..e3a02c1e3 --- /dev/null +++ b/app/src/main/java/com/celzero/bravedns/database/ConsoleLog.kt @@ -0,0 +1,26 @@ +/* + * Copyright 2024 RethinkDNS and its authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.celzero.bravedns.database + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity(tableName = "ConsoleLog") +data class ConsoleLog( + @PrimaryKey(autoGenerate = true) var id: Int = 0, + val message: String, + val timestamp: Long +) diff --git a/app/src/main/java/com/celzero/bravedns/database/ConsoleLogDAO.kt b/app/src/main/java/com/celzero/bravedns/database/ConsoleLogDAO.kt new file mode 100644 index 000000000..f66aed810 --- /dev/null +++ b/app/src/main/java/com/celzero/bravedns/database/ConsoleLogDAO.kt @@ -0,0 +1,49 @@ +/* + * Copyright 2024 RethinkDNS and its authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.celzero.bravedns.database + +import android.database.Cursor +import androidx.paging.PagingSource +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import androidx.room.RawQuery +import androidx.sqlite.db.SimpleSQLiteQuery + +@Dao +interface ConsoleLogDAO { + @Insert + suspend fun insert(log: ConsoleLog) + + @Query("SELECT * FROM ConsoleLog order by timestamp desc") + suspend fun getAllLogs(): List + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertBatch(log: List) + + @RawQuery + fun getLogsCursor(query: SimpleSQLiteQuery): Cursor + + @Query("select * from ConsoleLog order by id desc") + fun getLogs(): PagingSource + + @Query("DELETE FROM ConsoleLog WHERE id IN (SELECT id FROM ConsoleLog ORDER BY id ASC LIMIT :limit)") + suspend fun deleteOldLogs(limit: Int) + + @Query("select timestamp from ConsoleLog order by timestamp desc limit 1") + suspend fun sinceTime(): Long +} diff --git a/app/src/main/java/com/celzero/bravedns/database/ConsoleLogDatabase.kt b/app/src/main/java/com/celzero/bravedns/database/ConsoleLogDatabase.kt new file mode 100644 index 000000000..394418540 --- /dev/null +++ b/app/src/main/java/com/celzero/bravedns/database/ConsoleLogDatabase.kt @@ -0,0 +1,37 @@ +/* + * Copyright 2024 RethinkDNS and its authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.celzero.bravedns.database + +import android.content.Context +import androidx.room.Database +import androidx.room.Room +import androidx.room.RoomDatabase + +@Database(entities = [ConsoleLog::class], version = 1, exportSchema = false) +abstract class ConsoleLogDatabase : RoomDatabase() { + companion object { + fun buildDatabase(context: Context): ConsoleLogDatabase { + return Room.inMemoryDatabaseBuilder( + context.applicationContext, + ConsoleLogDatabase::class.java + ).build() + } + } + + abstract fun consoleLogDAO(): ConsoleLogDAO + + fun consoleLogRepository() = ConsoleLogRepository(consoleLogDAO()) +} diff --git a/app/src/main/java/com/celzero/bravedns/database/ConsoleLogRepository.kt b/app/src/main/java/com/celzero/bravedns/database/ConsoleLogRepository.kt new file mode 100644 index 000000000..5036577c2 --- /dev/null +++ b/app/src/main/java/com/celzero/bravedns/database/ConsoleLogRepository.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2024 RethinkDNS and its authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.celzero.bravedns.database + +class ConsoleLogRepository(private val consoleLogDAO: ConsoleLogDAO) { + + suspend fun insert(log: ConsoleLog) { + consoleLogDAO.insert(log) + } + + suspend fun deleteOldLogs(limit: Int) { + consoleLogDAO.deleteOldLogs(limit) + } + + suspend fun insertBatch(logs: List) { + consoleLogDAO.insertBatch(logs) + } + +} diff --git a/app/src/main/java/com/celzero/bravedns/database/DatabaseModule.kt b/app/src/main/java/com/celzero/bravedns/database/DatabaseModule.kt index e014272f7..621765c2a 100644 --- a/app/src/main/java/com/celzero/bravedns/database/DatabaseModule.kt +++ b/app/src/main/java/com/celzero/bravedns/database/DatabaseModule.kt @@ -15,6 +15,7 @@ */ package com.celzero.bravedns.database +import Logger import org.koin.android.ext.koin.androidContext import org.koin.dsl.module @@ -22,6 +23,7 @@ object DatabaseModule { private val databaseModule = module { single { AppDatabase.buildDatabase(androidContext()) } single { LogDatabase.buildDatabase(androidContext()) } + single { ConsoleLogDatabase.buildDatabase(androidContext()) } } private val daoModule = module { single { get().appInfoDAO() } @@ -45,6 +47,7 @@ object DatabaseModule { single { get().dotEndpointDao() } single { get().odohEndpointDao() } single { get().rethinkConnectionLogDAO() } + single { get().consoleLogDAO() } } private val repositoryModule = module { single { get().appInfoRepository() } @@ -68,6 +71,7 @@ object DatabaseModule { single { get().dotEndpointRepository() } single { get().odohEndpointRepository() } single { get().rethinkConnectionLogRepository() } + single { get().consoleLogRepository() } } val modules = listOf(databaseModule, daoModule, repositoryModule) diff --git a/app/src/main/java/com/celzero/bravedns/service/ConsoleLogManager.kt b/app/src/main/java/com/celzero/bravedns/service/ConsoleLogManager.kt new file mode 100644 index 000000000..85f5e11a9 --- /dev/null +++ b/app/src/main/java/com/celzero/bravedns/service/ConsoleLogManager.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2024 RethinkDNS and its authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.celzero.bravedns.service + +import com.celzero.bravedns.database.ConsoleLog +import com.celzero.bravedns.database.ConsoleLogRepository + +class ConsoleLogManager(private val repository: ConsoleLogRepository) { + + suspend fun insert(log: ConsoleLog) { + repository.insert(log) + } + + private suspend fun deleteOldLogs(limit: Int) { + repository.deleteOldLogs(limit) + } + + suspend fun insertBatch(logs: List<*>) { + val l = logs as? List ?: return + + repository.insertBatch(l) + } +} diff --git a/app/src/main/java/com/celzero/bravedns/service/NetLogTracker.kt b/app/src/main/java/com/celzero/bravedns/service/NetLogTracker.kt index 534881657..3cfb5219e 100644 --- a/app/src/main/java/com/celzero/bravedns/service/NetLogTracker.kt +++ b/app/src/main/java/com/celzero/bravedns/service/NetLogTracker.kt @@ -23,6 +23,8 @@ import com.celzero.bravedns.data.ConnTrackerMetaData import com.celzero.bravedns.data.ConnectionSummary import com.celzero.bravedns.database.ConnectionTracker import com.celzero.bravedns.database.ConnectionTrackerRepository +import com.celzero.bravedns.database.ConsoleLog +import com.celzero.bravedns.database.ConsoleLogRepository import com.celzero.bravedns.database.DnsLog import com.celzero.bravedns.database.DnsLogRepository import com.celzero.bravedns.database.RethinkLog @@ -36,6 +38,7 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.Job import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.awaitCancellation +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.newSingleThreadContext import kotlinx.coroutines.withContext @@ -49,6 +52,7 @@ internal constructor( connectionTrackerRepository: ConnectionTrackerRepository, rethinkLogRepository: RethinkLogRepository, dnsLogRepository: DnsLogRepository, + consoleLogRepository: ConsoleLogRepository, private val persistentState: PersistentState ) : KoinComponent { @@ -59,10 +63,12 @@ internal constructor( private var dnsdb: DnsLogTracker = DnsLogTracker(dnsLogRepository, persistentState, context) private var ipdb: IPTracker = IPTracker(connectionTrackerRepository, rethinkLogRepository, context) + private var consoleLogDb: ConsoleLogManager = ConsoleLogManager(consoleLogRepository) private var dnsBatcher: NetLogBatcher? = null private var ipBatcher: NetLogBatcher? = null private var rrBatcher :NetLogBatcher? = null + private var consoleLogBatcher: NetLogBatcher? = null // a single thread to run sig and batch co-routines in; // to avoid use of mutex/semaphores over shared-state @@ -70,6 +76,10 @@ internal constructor( @OptIn(DelicateCoroutinesApi::class, ExperimentalCoroutinesApi::class) private val looper = newSingleThreadContext("nlbLooper") + companion object { + private const val UPDATE_DELAY = 2500L + } + @OptIn(ExperimentalCoroutinesApi::class) suspend fun restart(s: CoroutineScope) { this.scope = s @@ -78,13 +88,17 @@ internal constructor( val b1 = NetLogBatcher("dns", looper, dnsdb::insertBatch) val b2 = NetLogBatcher("ip", looper, ipdb::insertBatch, ipdb::updateBatch) val b3 = NetLogBatcher("rr", looper, ipdb::insertRethinkBatch, ipdb::updateRethinkBatch) + val b4 = NetLogBatcher("lc", looper, consoleLogDb::insertBatch) b1.begin(s) b2.begin(s) b3.begin(s) + b4.begin(s) + this.dnsBatcher = b1 this.ipBatcher = b2 this.rrBatcher = b3 + this.consoleLogBatcher = b4 } fun writeIpLog(info: ConnTrackerMetaData) { @@ -107,15 +121,19 @@ internal constructor( fun updateIpSummary(summary: ConnectionSummary) { if (!persistentState.logsEnabled) return - + val d = Logger.LoggerType.fromId(persistentState.goLoggerLevel.toInt()) + val debug = d.isLessThan(Logger.LoggerType.DEBUG) io("updateIpSmm") { val s = - if (DEBUG && summary.targetIp?.isNotEmpty() == true) { + if (debug && summary.targetIp?.isNotEmpty() == true) { ipdb.makeSummaryWithTarget(summary) } else { summary } + // add a delay to ensure the insert is complete before updating + delay(UPDATE_DELAY) + ipBatcher?.update(s) } } @@ -131,6 +149,9 @@ internal constructor( summary } + // add a delay to ensure the insert is complete before updating + delay(UPDATE_DELAY) + rrBatcher?.update(s) } } @@ -153,6 +174,13 @@ internal constructor( io("writeDnsLog") { dnsBatcher?.add(dnsLog) } } + fun writeConsoleLog(msg: String) { + io("writeConsoleLog") { + val log = ConsoleLog(0, msg, System.currentTimeMillis()) + consoleLogBatcher?.add(log) + } + } + private fun io(s: String, f: suspend () -> Unit) = scope?.launch(CoroutineName(s) + Dispatchers.IO) { f() } } diff --git a/app/src/main/java/com/celzero/bravedns/service/ServiceModule.kt b/app/src/main/java/com/celzero/bravedns/service/ServiceModule.kt index 3018011b8..7c4df140a 100644 --- a/app/src/main/java/com/celzero/bravedns/service/ServiceModule.kt +++ b/app/src/main/java/com/celzero/bravedns/service/ServiceModule.kt @@ -23,7 +23,7 @@ object ServiceModule { private val serviceModules = module { single { PersistentState(androidContext()) } single { QueryTracker(get()) } - single { NetLogTracker(androidContext(), get(), get(), get(), get()) } + single { NetLogTracker(androidContext(), get(), get(), get(), get(), get()) } single { RefreshDatabase(androidContext(), get(), get(), get()) } } diff --git a/app/src/main/java/com/celzero/bravedns/service/VpnController.kt b/app/src/main/java/com/celzero/bravedns/service/VpnController.kt index 2d855754e..e82ced009 100644 --- a/app/src/main/java/com/celzero/bravedns/service/VpnController.kt +++ b/app/src/main/java/com/celzero/bravedns/service/VpnController.kt @@ -31,6 +31,7 @@ import com.celzero.bravedns.util.Constants.Companion.INVALID_UID import com.celzero.bravedns.util.Utilities import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.channels.consumeEach @@ -319,4 +320,8 @@ object VpnController : KoinComponent { suspend fun goBuildVersion(): String { return braveVpnService?.goBuildVersion() ?: "" } + + fun writeConsoleLog(message: String) { + braveVpnService?.writeConsoleLog(message) + } } diff --git a/app/src/play/java/com/celzero/bravedns/util/Logger.kt b/app/src/play/java/com/celzero/bravedns/util/Logger.kt index ec7b82c9f..bbd880996 100644 --- a/app/src/play/java/com/celzero/bravedns/util/Logger.kt +++ b/app/src/play/java/com/celzero/bravedns/util/Logger.kt @@ -16,6 +16,7 @@ import android.util.Log import com.celzero.bravedns.service.PersistentState import com.celzero.bravedns.util.Utilities +import com.celzero.bravedns.service.VpnController import com.google.firebase.crashlytics.FirebaseCrashlytics import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -42,7 +43,7 @@ object Logger : KoinComponent { const val LOG_GO_LOGGER = "LibLogger" // github.com/celzero/firestack/blob/bce8de917fec5e48a41ed1e96c9d942ee0f7996b/intra/log/logger.go#L76 - enum class LoggerType(val id: Int) { + enum class LoggerType(val id: Long) { VERY_VERBOSE(0), VERBOSE(1), DEBUG(2), @@ -74,6 +75,10 @@ object Logger : KoinComponent { return this == STACKTRACE } + fun isLessThan(level: LoggerType): Boolean { + return this.id < level.id + } + fun user(): Boolean { return this == USR } @@ -155,5 +160,18 @@ object Logger : KoinComponent { LoggerType.USR -> {} // Do nothing LoggerType.NONE -> {} // Do nothing } + if (type.id >= logLevel) { + // get the first letter of the level and append it to the tag + val l = when (type) { + LoggerType.VERBOSE -> "V" + LoggerType.DEBUG -> "D" + LoggerType.INFO -> "I" + LoggerType.WARN -> "W" + LoggerType.ERROR -> "E" + LoggerType.STACKTRACE -> "E" + else -> "V" + } + VpnController.writeConsoleLog("$l $tag: $msg") + } } } diff --git a/app/src/website/java/com/celzero/bravedns/util/Logger.kt b/app/src/website/java/com/celzero/bravedns/util/Logger.kt index fc00cf73d..e95a8bbdd 100644 --- a/app/src/website/java/com/celzero/bravedns/util/Logger.kt +++ b/app/src/website/java/com/celzero/bravedns/util/Logger.kt @@ -15,7 +15,7 @@ */ import android.util.Log import com.celzero.bravedns.service.PersistentState -import com.celzero.bravedns.util.Utilities +import com.celzero.bravedns.service.VpnController import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -41,7 +41,7 @@ object Logger : KoinComponent { const val LOG_GO_LOGGER = "LibLogger" // github.com/celzero/firestack/blob/bce8de917fec5e48a41ed1e96c9d942ee0f7996b/intra/log/logger.go#L76 - enum class LoggerType(val id: Int) { + enum class LoggerType(val id: Long) { VERY_VERBOSE(0), VERBOSE(1), DEBUG(2), @@ -73,6 +73,10 @@ object Logger : KoinComponent { return this == STACKTRACE } + fun isLessThan(level: LoggerType): Boolean { + return this.id < level.id + } + fun user(): Boolean { return this == USR } @@ -138,5 +142,18 @@ object Logger : KoinComponent { LoggerType.USR -> {} // Do nothing LoggerType.NONE -> {} // Do nothing } + if (type.id >= logLevel) { + // get the first letter of the level and append it to the tag + val l = when (type) { + LoggerType.VERBOSE -> "V" + LoggerType.DEBUG -> "D" + LoggerType.INFO -> "I" + LoggerType.WARN -> "W" + LoggerType.ERROR -> "E" + LoggerType.STACKTRACE -> "E" + else -> "V" + } + VpnController.writeConsoleLog("$l $tag: $msg") + } } } From df29defedab31b1b788185695463c7602d741fd2 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 24 Jun 2024 20:49:56 +0530 Subject: [PATCH 007/188] ui: new ui to show, save, and share console logs --- app/src/full/AndroidManifest.xml | 2 + .../celzero/bravedns/RethinkDnsApplication.kt | 1 + .../bravedns/adapter/ConsoleLogAdapter.kt | 100 +++++ .../bravedns/scheduler/LogExportWorker.kt | 74 ++++ .../bravedns/scheduler/PurgeConsoleLogs.kt | 26 ++ .../bravedns/scheduler/WorkScheduler.kt | 40 ++ .../ui/activity/ConsoleLogActivity.kt | 384 ++++++++++++++++++ .../ui/activity/MiscSettingsActivity.kt | 9 + .../ui/activity/NetworkLogsActivity.kt | 9 + .../bravedns/viewmodel/ConsoleLogViewModel.kt | 51 +++ .../bravedns/viewmodel/ViewModelModule.kt | 1 + .../full/res/layout/activity_console_log.xml | 97 +++++ .../res/layout/activity_misc_settings.xml | 169 +++++--- .../main/res/layout/list_item_console_log.xml | 41 ++ .../bravedns/RethinkDnsApplicationPlay.kt | 1 + 15 files changed, 950 insertions(+), 55 deletions(-) create mode 100644 app/src/full/java/com/celzero/bravedns/adapter/ConsoleLogAdapter.kt create mode 100644 app/src/full/java/com/celzero/bravedns/scheduler/LogExportWorker.kt create mode 100644 app/src/full/java/com/celzero/bravedns/scheduler/PurgeConsoleLogs.kt create mode 100644 app/src/full/java/com/celzero/bravedns/ui/activity/ConsoleLogActivity.kt create mode 100644 app/src/full/java/com/celzero/bravedns/viewmodel/ConsoleLogViewModel.kt create mode 100644 app/src/full/res/layout/activity_console_log.xml create mode 100644 app/src/main/res/layout/list_item_console_log.xml diff --git a/app/src/full/AndroidManifest.xml b/app/src/full/AndroidManifest.xml index d5cb6e6bb..5b4a68bb0 100644 --- a/app/src/full/AndroidManifest.xml +++ b/app/src/full/AndroidManifest.xml @@ -113,6 +113,8 @@ + ().scheduleDatabaseRefreshJob() get().scheduleDataUsageJob() get().schedulePurgeConnectionsLog() + get().schedulePurgeConsoleLogs() } private fun turnOnStrictMode() { diff --git a/app/src/full/java/com/celzero/bravedns/adapter/ConsoleLogAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/ConsoleLogAdapter.kt new file mode 100644 index 000000000..07bb3b2b9 --- /dev/null +++ b/app/src/full/java/com/celzero/bravedns/adapter/ConsoleLogAdapter.kt @@ -0,0 +1,100 @@ +/* + * Copyright 2024 RethinkDNS and its authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.celzero.bravedns.adapter + +import android.content.Context +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.paging.PagingDataAdapter +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.celzero.bravedns.R +import com.celzero.bravedns.RethinkDnsApplication.Companion.DEBUG +import com.celzero.bravedns.database.ConsoleLog +import com.celzero.bravedns.databinding.ListItemConsoleLogBinding +import com.celzero.bravedns.util.Constants.Companion.TIME_FORMAT_1 +import com.celzero.bravedns.util.UIUtils +import com.celzero.bravedns.util.Utilities + +class ConsoleLogAdapter(private val context: Context) : + PagingDataAdapter(DIFF_CALLBACK) { + + companion object { + private val DIFF_CALLBACK = + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame(old: ConsoleLog, new: ConsoleLog): Boolean { + return old == new + } + + override fun areContentsTheSame(old: ConsoleLog, new: ConsoleLog): Boolean { + return old == new + } + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ConsoleLogViewHolder { + val itemBinding = + ListItemConsoleLogBinding.inflate(LayoutInflater.from(parent.context), parent, false) + return ConsoleLogViewHolder(itemBinding) + } + + override fun onBindViewHolder(holder: ConsoleLogViewHolder, position: Int) { + val logInfo: ConsoleLog = getItem(position) ?: return + holder.update(logInfo) + } + + inner class ConsoleLogViewHolder(private val b: ListItemConsoleLogBinding) : + RecyclerView.ViewHolder(b.root) { + + fun update(log: ConsoleLog) { + // update the textview color with the first letter of the log level + val logLevel = log.message.first() + when (logLevel) { + 'V' -> + b.logDetail.setTextColor( + UIUtils.fetchColor(context, R.attr.primaryLightColorText) + ) + 'D' -> + b.logDetail.setTextColor( + UIUtils.fetchColor(context, R.attr.primaryLightColorText) + ) + 'I' -> + b.logDetail.setTextColor( + UIUtils.fetchColor(context, R.attr.defaultToggleBtnTxt) + ) + 'W' -> + b.logDetail.setTextColor( + UIUtils.fetchColor(context, R.attr.firewallWhiteListToggleBtnTxt) + ) + 'E' -> + b.logDetail.setTextColor( + UIUtils.fetchColor(context, R.attr.firewallBlockToggleBtnTxt) + ) + else -> + b.logDetail.setTextColor( + UIUtils.fetchColor(context, R.attr.firewallNoRuleToggleBtnTxt) + ) + } + b.logDetail.text = log.message + if (DEBUG) { + b.logTimestamp.text = + " ${log.id} ${Utilities.convertLongToTime(log.timestamp, TIME_FORMAT_1)}" + } else { + b.logTimestamp.text = Utilities.convertLongToTime(log.timestamp, TIME_FORMAT_1) + } + } + } +} diff --git a/app/src/full/java/com/celzero/bravedns/scheduler/LogExportWorker.kt b/app/src/full/java/com/celzero/bravedns/scheduler/LogExportWorker.kt new file mode 100644 index 000000000..60d8b155d --- /dev/null +++ b/app/src/full/java/com/celzero/bravedns/scheduler/LogExportWorker.kt @@ -0,0 +1,74 @@ +package com.celzero.bravedns.scheduler + +import Logger +import Logger.LOG_TAG_BUG_REPORT +import android.content.Context +import android.database.Cursor +import androidx.sqlite.db.SimpleSQLiteQuery +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import com.celzero.bravedns.database.ConsoleLogDAO +import java.io.File +import java.io.FileOutputStream +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import java.io.BufferedOutputStream +import java.util.zip.ZipFile + +class LogExportWorker(context: Context, workerParams: WorkerParameters) : + CoroutineWorker(context, workerParams), KoinComponent { + + private val consoleLogDao by inject() + + override suspend fun doWork(): Result { + return try { + val filePath = inputData.getString("filePath") ?: return Result.failure() + Logger.i(LOG_TAG_BUG_REPORT, "Exporting logs to $filePath") + exportLogsToCsvStream(filePath) + Result.success() + } catch (e: Exception) { + Result.failure() + } + } + + private fun exportLogsToCsvStream(filePath: String): Boolean { + var cursor: Cursor? = null + try { + val query = SimpleSQLiteQuery("SELECT * FROM ConsoleLog order by id") + cursor = consoleLogDao.getLogsCursor(query) + + val file = File(filePath) + if (file.exists()) { + Logger.v(LOG_TAG_BUG_REPORT, "Deleting existing zip file, ${file.absolutePath}") + file.delete() + } + + ZipOutputStream(BufferedOutputStream(FileOutputStream(filePath))).use { zos -> + val zipEntry = ZipEntry("log.txt") + zos.putNextEntry(zipEntry) + if (cursor.moveToFirst()) { + do { + val timestamp = + cursor.getLong(cursor.getColumnIndexOrThrow("timestamp")) + val message = + cursor.getString(cursor.getColumnIndexOrThrow("message")) + val logEntry = "$timestamp,$message\n" + zos.write(logEntry.toByteArray()) + } while (cursor.moveToNext()) + } + zos.closeEntry() + } + cursor.close() + + Logger.i(LOG_TAG_BUG_REPORT, "Logs exported to ${file.absolutePath}") + return true + } catch (e: Exception) { + Logger.e(LOG_TAG_BUG_REPORT, "Error exporting logs", e) + } finally{ + cursor?.close() + } + return false + } +} diff --git a/app/src/full/java/com/celzero/bravedns/scheduler/PurgeConsoleLogs.kt b/app/src/full/java/com/celzero/bravedns/scheduler/PurgeConsoleLogs.kt new file mode 100644 index 000000000..9570bc391 --- /dev/null +++ b/app/src/full/java/com/celzero/bravedns/scheduler/PurgeConsoleLogs.kt @@ -0,0 +1,26 @@ +package com.celzero.bravedns.scheduler + +import android.content.Context +import androidx.work.CoroutineWorker +import androidx.work.WorkerParameters +import com.celzero.bravedns.database.ConsoleLogRepository +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +class PurgeConsoleLogs(val context: Context, workerParameters: WorkerParameters) : + CoroutineWorker(context, workerParameters), KoinComponent { + + companion object { + const val LOG_LIMIT = 20000 + } + + private val consoleLogRepository by inject() + + override suspend fun doWork(): Result { + // delete old logs will limit the logs count to 20000 + consoleLogRepository.deleteOldLogs(LOG_LIMIT) + Logger.v(Logger.LOG_TAG_APP_DB, "Purging console logs") + return Result.success() + } + +} diff --git a/app/src/full/java/com/celzero/bravedns/scheduler/WorkScheduler.kt b/app/src/full/java/com/celzero/bravedns/scheduler/WorkScheduler.kt index 624682d3a..d529b4fa6 100644 --- a/app/src/full/java/com/celzero/bravedns/scheduler/WorkScheduler.kt +++ b/app/src/full/java/com/celzero/bravedns/scheduler/WorkScheduler.kt @@ -20,6 +20,7 @@ import Logger import Logger.LOG_TAG_SCHEDULER import android.content.Context import androidx.work.BackoffPolicy +import androidx.work.Data import androidx.work.ExistingPeriodicWorkPolicy import androidx.work.ExistingWorkPolicy import androidx.work.OneTimeWorkRequestBuilder @@ -38,11 +39,14 @@ class WorkScheduler(val context: Context) { const val APP_EXIT_INFO_ONE_TIME_JOB_TAG = "OnDemandCollectAppExitInfoJob" const val APP_EXIT_INFO_JOB_TAG = "ScheduledCollectAppExitInfoJob" const val PURGE_CONNECTION_LOGS_JOB_TAG = "ScheduledPurgeConnectionLogsJob" + const val PURGE_CONSOLE_LOGS_JOB_TAG = "ScheduledPurgeConsoleLogsJob" const val BLOCKLIST_UPDATE_CHECK_JOB_TAG = "ScheduledBlocklistUpdateCheckJob" const val DATA_USAGE_JOB_TAG = "ScheduledDataUsageJob" + const val CONSOLE_LOG_SAVE_JOB_TAG = "ConsoleLogSaveJob" const val APP_EXIT_INFO_JOB_TIME_INTERVAL_DAYS: Long = 7 const val PURGE_LOGS_TIME_INTERVAL_HOURS: Long = 4 + const val PURGE_CONSOLE_LOGS_TIME_INTERVAL_HOURS: Long = 4 const val BLOCKLIST_UPDATE_CHECK_INTERVAL_DAYS: Long = 3 const val DATA_USAGE_TIME_INTERVAL_MINS: Long = 20 @@ -140,6 +144,25 @@ class WorkScheduler(val context: Context) { ) } + fun schedulePurgeConsoleLogs() { + val purgeLogs = + PeriodicWorkRequest.Builder( + PurgeConsoleLogs::class.java, + PURGE_CONSOLE_LOGS_TIME_INTERVAL_HOURS, + TimeUnit.HOURS + ) + .addTag(PURGE_CONSOLE_LOGS_JOB_TAG) + .build() + + Logger.d(LOG_TAG_SCHEDULER, "purge console logs job scheduled") + WorkManager.getInstance(context.applicationContext) + .enqueueUniquePeriodicWork( + PURGE_CONSOLE_LOGS_JOB_TAG, + ExistingPeriodicWorkPolicy.CANCEL_AND_REENQUEUE, + purgeLogs + ) + } + // Schedule AppExitInfo on demand fun scheduleOneTimeWorkForAppExitInfo() { val bugReportCollector = @@ -199,4 +222,21 @@ class WorkScheduler(val context: Context) { workRequest ) } + + fun scheduleConsoleLogSaveJob(filePath: String) { + Logger.i(LOG_TAG_SCHEDULER, "Console log save job scheduled") + val inputData = Data.Builder().putString("filePath", filePath).build() + val workRequest = + OneTimeWorkRequestBuilder() + .addTag(CONSOLE_LOG_SAVE_JOB_TAG) + .setInputData(inputData) + .build() + + WorkManager.getInstance(context.applicationContext) + .enqueueUniqueWork( + CONSOLE_LOG_SAVE_JOB_TAG, + ExistingWorkPolicy.REPLACE, + workRequest + ) + } } diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/ConsoleLogActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/ConsoleLogActivity.kt new file mode 100644 index 000000000..3c92f0ba1 --- /dev/null +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/ConsoleLogActivity.kt @@ -0,0 +1,384 @@ +/* + * Copyright 2024 RethinkDNS and its authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.celzero.bravedns.ui.activity + +import Logger.LOG_TAG_BUG_REPORT +import android.Manifest +import android.content.Context +import android.content.Intent +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.content.res.Configuration +import android.net.Uri +import android.os.Bundle +import android.os.Environment +import android.provider.Settings +import android.view.View +import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import androidx.core.content.FileProvider +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.RecyclerView +import androidx.work.WorkInfo +import androidx.work.WorkManager +import by.kirich1409.viewbindingdelegate.viewBinding +import com.celzero.bravedns.R +import com.celzero.bravedns.adapter.ConsoleLogAdapter +import com.celzero.bravedns.databinding.ActivityConsoleLogBinding +import com.celzero.bravedns.scheduler.WorkScheduler +import com.celzero.bravedns.service.PersistentState +import com.celzero.bravedns.util.Constants +import com.celzero.bravedns.util.CustomLinearLayoutManager +import com.celzero.bravedns.util.Themes +import com.celzero.bravedns.util.Utilities +import com.celzero.bravedns.util.Utilities.isAtleastR +import com.celzero.bravedns.viewmodel.ConsoleLogViewModel +import java.io.File +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.koin.android.ext.android.inject + +class ConsoleLogActivity : AppCompatActivity(R.layout.activity_console_log) { + + private val b by viewBinding(ActivityConsoleLogBinding::bind) + private var layoutManager: RecyclerView.LayoutManager? = null + private val persistentState by inject() + + private val consoleLogViewModel by inject() + private val workScheduler by inject() + + companion object { + private const val FOLDER_NAME = "Rethink" + private const val SCHEME_PACKAGE = "package" + private const val FILE_NAME = "rethink_app_logs_" + private const val FILE_EXTENSION = ".zip" + private const val STORAGE_PERMISSION_CODE = 231 // request code for storage permission + } + + enum class SaveType { + SAVE, + SHARE; + + fun isSave(): Boolean { + return this == SAVE + } + + fun isShare(): Boolean { + return this == SHARE + } + } + + private fun Context.isDarkThemeOn(): Boolean { + return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == + Configuration.UI_MODE_NIGHT_YES + } + + override fun onCreate(savedInstanceState: Bundle?) { + setTheme(Themes.getCurrentTheme(isDarkThemeOn(), persistentState.theme)) + super.onCreate(savedInstanceState) + initView() + setupClickListener() + } + + private fun initView() { + setAdapter() + io { + val sinceTime = consoleLogViewModel.sinceTime() + val since = Utilities.convertLongToTime(sinceTime, Constants.TIME_FORMAT_3) + uiCtx { + val desc = getString(R.string.console_log_desc) + val sinceTxt = getString(R.string.logs_card_duration, since) + val descWithTime = getString(R.string.two_argument_space, desc, sinceTxt) + b.consoleLogInfoText.text = descWithTime + } + } + } + + private fun setAdapter() { + var shouldScroll = true + b.consoleLogList.setHasFixedSize(true) + layoutManager = CustomLinearLayoutManager(this) + b.consoleLogList.layoutManager = layoutManager + val recyclerAdapter = ConsoleLogAdapter(this) + consoleLogViewModel.logs.observe(this) { + recyclerAdapter.submitData(this.lifecycle, it) + if (b.consoleLogList.scrollState == RecyclerView.SCROLL_STATE_IDLE && shouldScroll) { + b.consoleLogList.post { + b.consoleLogList.scrollToPosition(0) // scroll to top if not scrolling + } + } else if (b.consoleLogList.scrollState == RecyclerView.SCROLL_STATE_DRAGGING) { + shouldScroll = false // user is scrolling, don't scroll to top + } else if (b.consoleLogList.scrollState == RecyclerView.SCROLL_STATE_SETTLING) { + shouldScroll = false // user is scrolling, don't scroll to top + } + } + b.consoleLogList.adapter = recyclerAdapter + } + + private fun setupClickListener() { + b.consoleLogSave.setOnClickListener { + val filePath = createFile(SaveType.SAVE) + if (filePath == null) { + showFileCreationErrorToast() + return@setOnClickListener + } + handleSaveOrShareLogs(filePath, SaveType.SAVE) + } + + b.consoleLogShare.setOnClickListener { + val filePath = createFile(SaveType.SHARE) + if (filePath == null) { + showFileCreationErrorToast() + return@setOnClickListener + } + handleSaveOrShareLogs(filePath, SaveType.SHARE) + } + b.fabShareLog.setOnClickListener { + val filePath = createFile(SaveType.SHARE) + if (filePath == null) { + showFileCreationErrorToast() + return@setOnClickListener + } + handleSaveOrShareLogs(filePath, SaveType.SHARE) + } + } + + private fun handleSaveOrShareLogs(filePath: String, type: SaveType) { + if (WorkScheduler.isWorkRunning(this, WorkScheduler.CONSOLE_LOG_SAVE_JOB_TAG)) return + + workScheduler.scheduleConsoleLogSaveJob(filePath) + showLogGenerationProgressUi() + + val workManager = WorkManager.getInstance(this.applicationContext) + workManager.getWorkInfosByTagLiveData(WorkScheduler.CONSOLE_LOG_SAVE_JOB_TAG).observe( + this + ) { workInfoList -> + val workInfo = workInfoList?.getOrNull(0) ?: return@observe + Logger.i( + Logger.LOG_TAG_SCHEDULER, + "WorkManager state: ${workInfo.state} for ${WorkScheduler.CONSOLE_LOG_SAVE_JOB_TAG}" + ) + if (WorkInfo.State.SUCCEEDED == workInfo.state) { + onSuccess() + if (type.isShare()) { + shareZipFileViaEmail(filePath) + } + workManager.pruneWork() + } else if ( + WorkInfo.State.CANCELLED == workInfo.state || + WorkInfo.State.FAILED == workInfo.state + ) { + onFailure() + workManager.pruneWork() + workManager.cancelAllWorkByTag(WorkScheduler.CONSOLE_LOG_SAVE_JOB_TAG) + } else { // state == blocked, queued, or running + // no-op + } + } + } + + private fun onSuccess() { + // show success message + Logger.i(LOG_TAG_BUG_REPORT, "Logs saved successfully") + b.consoleLogProgressBar.visibility = View.GONE + Toast.makeText(this, "Logs saved successfully", Toast.LENGTH_LONG).show() + } + + private fun onFailure() { + // show failure message + Logger.i(LOG_TAG_BUG_REPORT, "Logs save failed") + b.consoleLogProgressBar.visibility = View.GONE + Toast.makeText(this, "Logs save failed", Toast.LENGTH_LONG).show() + } + + private fun showLogGenerationProgressUi() { + // show progress dialog or progress bar + Logger.i(LOG_TAG_BUG_REPORT, "Logs generation in progress") + b.consoleLogProgressBar.visibility = View.VISIBLE + } + + private fun createFile(type: SaveType): String? { + if (type.isShare()) { + // create file in localdir and share, no need to check for permissions + return makeConsoleLogFile(type) + } + + // check for storage permissions + if (!checkStoragePermissions()) { + // request for storage permissions + Logger.i(LOG_TAG_BUG_REPORT, "requesting for storage permissions") + requestForStoragePermissions() + return null + } + + Logger.i(LOG_TAG_BUG_REPORT, "storage permission granted, creating pcap file") + try { + val filePath = makeConsoleLogFile(type) + if (filePath == null) { + showFileCreationErrorToast() + return null + } + return filePath + } catch (e: Exception) { + showFileCreationErrorToast() + } + return null + } + + private fun shareZipFileViaEmail(filePath: String) { + val file = File(filePath) + // Get the URI of the file using FileProvider + val uri: Uri = FileProvider.getUriForFile(this, "${this.packageName}.provider", file) + + // Create the intent + val intent = + Intent(Intent.ACTION_SEND).apply { + type = "application/zip" + putExtra(Intent.EXTRA_SUBJECT, "Log File") + putExtra(Intent.EXTRA_TEXT, "Please find the attached log file.") + putExtra(Intent.EXTRA_STREAM, uri) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } + + // Start the email app + startActivity(Intent.createChooser(intent, "Send email...")) + } + + private fun makeConsoleLogFile(type: SaveType): String? { + return try { + val appVersion = getVersionName() + return if (type.isShare()) { + // create file in filesdir, no need to check for permissions + val dir = filesDir.canonicalPath + File.separator + val fileName: String = FILE_NAME + appVersion + FILE_EXTENSION + val file = File(dir, fileName) + if (!file.exists()) { + file.createNewFile() + } + file.absolutePath + } else { + // create folder in DOWNLOADS + val dir = + if (isAtleastR()) { + val downloadsDir = + Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DOWNLOADS + ) + // create folder in DOWNLOADS/Rethink + File(downloadsDir, FOLDER_NAME) + } else { + val downloadsDir = Environment.getExternalStorageDirectory() + // create folder in DOWNLOADS/Rethink + File(downloadsDir, FOLDER_NAME) + } + if (!dir.exists()) { + dir.mkdirs() + } + // filename format (rethink_app_logs_.txt) + val logFileName: String = FILE_NAME + appVersion + FILE_EXTENSION + val file = File(dir, logFileName) + // just in case, create the parent dir if it doesn't exist + if (file.parentFile?.exists() != true) file.parentFile?.mkdirs() + // create the file if it doesn't exist + if (!file.exists()) { + file.createNewFile() + } + file.absolutePath + } + } catch (e: Exception) { + Logger.e(LOG_TAG_BUG_REPORT, "error creating log file", e) + null + } + } + + private fun getVersionName(): String { + val pInfo: PackageInfo? = + Utilities.getPackageMetadata(this.packageManager, this.packageName) + return pInfo?.versionName ?: "" + } + + private fun showFileCreationErrorToast() { + // show toast message + } + + private fun requestForStoragePermissions() { + // version 11 (R) or above + if (isAtleastR()) { + try { + val intent = Intent() + intent.action = Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION + val uri = Uri.fromParts(SCHEME_PACKAGE, this.packageName, null) + intent.data = uri + storageActivityResultLauncher.launch(intent) + } catch (e: Exception) { + val intent = Intent() + intent.action = Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION + storageActivityResultLauncher.launch(intent) + } + } else { + // below version 11 + ActivityCompat.requestPermissions( + this, + arrayOf( + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_EXTERNAL_STORAGE + ), + STORAGE_PERMISSION_CODE + ) + } + } + + private val storageActivityResultLauncher: ActivityResultLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (isAtleastR()) { + // version 11 (R) or above + if (Environment.isExternalStorageManager()) { + createFile(SaveType.SAVE) + } else { + showFileCreationErrorToast() + } + } else { + // below ver 11 (R), the permission is handled via onRequestPermissionsResult + } + } + + private fun checkStoragePermissions(): Boolean { + return if (isAtleastR()) { + // version 11 (R) or above + Environment.isExternalStorageManager() + } else { + // below version 11 + val write = + ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) + val read = + ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) + read == PackageManager.PERMISSION_GRANTED && write == PackageManager.PERMISSION_GRANTED + } + } + + private fun io(f: suspend () -> Unit) { + lifecycleScope.launch(Dispatchers.IO) { f() } + } + + private suspend fun uiCtx(f: () -> Unit) { + withContext(Dispatchers.Main) { f() } + } +} diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/MiscSettingsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/MiscSettingsActivity.kt index eac3b17ef..ecc221d5f 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/MiscSettingsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/MiscSettingsActivity.kt @@ -333,6 +333,15 @@ class MiscSettingsActivity : AppCompatActivity(R.layout.activity_misc_settings) Logger.disableCrashlytics() } } + + b.settingsConsoleLogRl.setOnClickListener { + openConsoleLogActivity() + } + } + + private fun openConsoleLogActivity() { + val intent = Intent(this, ConsoleLogActivity::class.java) + startActivity(intent) } private fun invokeChangeLocaleDialog() { diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/NetworkLogsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/NetworkLogsActivity.kt index 929ddb68f..196941e92 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/NetworkLogsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/NetworkLogsActivity.kt @@ -87,6 +87,15 @@ class NetworkLogsActivity : AppCompatActivity(R.layout.activity_network_logs) { b.logsActViewpager.setCurrentItem(fragmentIndex, false) observeAppState() + + b.appLogs.setOnClickListener { + openConsoleLogActivity() + } + } + + private fun openConsoleLogActivity() { + val intent = Intent(this, ConsoleLogActivity::class.java) + startActivity(intent) } private fun getCount(): Int { diff --git a/app/src/full/java/com/celzero/bravedns/viewmodel/ConsoleLogViewModel.kt b/app/src/full/java/com/celzero/bravedns/viewmodel/ConsoleLogViewModel.kt new file mode 100644 index 000000000..d8ff386ee --- /dev/null +++ b/app/src/full/java/com/celzero/bravedns/viewmodel/ConsoleLogViewModel.kt @@ -0,0 +1,51 @@ +/* + * Copyright 2024 RethinkDNS and its authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.celzero.bravedns.viewmodel + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.switchMap +import androidx.lifecycle.viewModelScope +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.PagingData +import androidx.paging.cachedIn +import androidx.paging.liveData +import com.celzero.bravedns.database.ConsoleLog +import com.celzero.bravedns.database.ConsoleLogDAO +import com.celzero.bravedns.util.Constants + +class ConsoleLogViewModel(private val consoleLogDAO: ConsoleLogDAO) : ViewModel() { + private var filter: MutableLiveData = MutableLiveData() + + init { + filter.value = "" + } + + val logs = filter.switchMap { input: String -> getLogs(input) } + + private fun getLogs(filter: String): LiveData> { + // filter is unused for now + return Pager(PagingConfig(Constants.LIVEDATA_PAGE_SIZE)) { consoleLogDAO.getLogs() } + .liveData + .cachedIn(viewModelScope) + } + + suspend fun sinceTime(): Long { + return consoleLogDAO.sinceTime() + } +} diff --git a/app/src/full/java/com/celzero/bravedns/viewmodel/ViewModelModule.kt b/app/src/full/java/com/celzero/bravedns/viewmodel/ViewModelModule.kt index a1caedf18..fa9ea9862 100644 --- a/app/src/full/java/com/celzero/bravedns/viewmodel/ViewModelModule.kt +++ b/app/src/full/java/com/celzero/bravedns/viewmodel/ViewModelModule.kt @@ -44,6 +44,7 @@ object ViewModelModule { viewModel { ODoHEndpointViewModel(get()) } viewModel { RethinkLogViewModel(get()) } viewModel { AlertsViewModel(get(), get()) } + viewModel { ConsoleLogViewModel(get()) } } val modules = listOf(viewModelModule) diff --git a/app/src/full/res/layout/activity_console_log.xml b/app/src/full/res/layout/activity_console_log.xml new file mode 100644 index 000000000..a66a1b106 --- /dev/null +++ b/app/src/full/res/layout/activity_console_log.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/full/res/layout/activity_misc_settings.xml b/app/src/full/res/layout/activity_misc_settings.xml index b465ed94a..4c984c5f3 100644 --- a/app/src/full/res/layout/activity_misc_settings.xml +++ b/app/src/full/res/layout/activity_misc_settings.xml @@ -155,6 +155,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -636,62 +750,7 @@ - - - - - - - - - - - - - + + + + + + + + + + diff --git a/app/src/play/java/com/celzero/bravedns/RethinkDnsApplicationPlay.kt b/app/src/play/java/com/celzero/bravedns/RethinkDnsApplicationPlay.kt index e30709eb0..f316b38aa 100644 --- a/app/src/play/java/com/celzero/bravedns/RethinkDnsApplicationPlay.kt +++ b/app/src/play/java/com/celzero/bravedns/RethinkDnsApplicationPlay.kt @@ -63,5 +63,6 @@ class RethinkDnsApplicationPlay : Application() { get().scheduleDatabaseRefreshJob() get().scheduleDataUsageJob() get().schedulePurgeConnectionsLog() + get().schedulePurgeConsoleLogs() } } From ee9262c9657c2c3491bb02dd8df1c192fdad1f3a Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 24 Jun 2024 20:51:22 +0530 Subject: [PATCH 008/188] ui: correct wg stats update using coroutines --- .../bravedns/adapter/OneWgConfigAdapter.kt | 35 ++++++++----- .../bravedns/adapter/WgConfigAdapter.kt | 51 ++++++++----------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt index 7f8316930..c7763ea28 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt @@ -100,13 +100,20 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns parent, false ) - lifecycleOwner = parent.findViewTreeLifecycleOwner() + if (lifecycleOwner == null) { + lifecycleOwner = parent.findViewTreeLifecycleOwner() + } return WgInterfaceViewHolder(itemBinding) } + override fun onViewDetachedFromWindow(holder: WgInterfaceViewHolder) { + super.onViewDetachedFromWindow(holder) + holder.cancelJobIfAny() + } + inner class WgInterfaceViewHolder(private val b: ListItemWgOneInterfaceBinding) : RecyclerView.ViewHolder(b.root) { - private var statusCheckJob: Job? = null + private var job: Job? = null fun update(config: WgConfigFiles) { b.interfaceNameText.text = config.name @@ -116,17 +123,20 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns if (isWgActive) { keepStatusUpdated(config) } else { + cancelJobIfAny() disableInterface() } } - private fun keepStatusUpdated(config: WgConfigFiles) { - if (statusCheckJob?.isActive == true) return - - statusCheckJob = io { - for (i in 0 until 10) { - if (statusCheckJob?.isActive == false) return@io + fun cancelJobIfAny() { + if (job?.isActive == true) { + job?.cancel() + } + } + private fun keepStatusUpdated(config: WgConfigFiles) { + job = io { + while (true) { updateStatus(config) delay(ONE_SEC) } @@ -169,7 +179,7 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns ?.currentState ?.isAtLeast(androidx.lifecycle.Lifecycle.State.STARTED) == false ) { - statusCheckJob?.cancel() + job?.cancel() return } @@ -279,7 +289,6 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns } private fun disableInterface() { - statusCheckJob?.cancel() b.interfaceDetailCard.strokeWidth = 0 b.protocolInfoChipGroup.visibility = View.GONE b.interfaceAppsCount.visibility = View.GONE @@ -315,7 +324,7 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns R.string.symbol_upload, Utilities.humanReadableByteCount(stats.tx, true) ) - return context.getString(R.string.two_argument_space, rx, tx) + return context.getString(R.string.two_argument_space, tx, rx) } private fun getHandshakeTime(stats: Stats?): CharSequence { @@ -418,13 +427,13 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns if (!VpnController.hasTunnel()) { Logger.i(LOG_TAG_PROXY, "VPN not active, cannot disable WireGuard") uiCtx { + // reset the check box + b.oneWgCheck.isChecked = true Utilities.showToastUiCentered( context, ERR_CODE_VPN_NOT_ACTIVE + context.getString(R.string.settings_socks5_vpn_disabled_error), Toast.LENGTH_LONG ) - // reset the check box - b.oneWgCheck.isChecked = true } return } diff --git a/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt index 2db0b6bd7..aee09413a 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt @@ -46,7 +46,6 @@ import com.celzero.bravedns.ui.activity.WgConfigDetailActivity import com.celzero.bravedns.ui.activity.WgConfigEditorActivity.Companion.INTENT_EXTRA_WG_ID import com.celzero.bravedns.util.UIUtils import com.celzero.bravedns.util.Utilities -import java.util.concurrent.ConcurrentHashMap import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.delay @@ -56,7 +55,6 @@ import kotlinx.coroutines.withContext class WgConfigAdapter(private val context: Context) : PagingDataAdapter(DIFF_CALLBACK) { - private var configs: ConcurrentHashMap = ConcurrentHashMap() private var lifecycleOwner: LifecycleOwner? = null companion object { @@ -97,18 +95,20 @@ class WgConfigAdapter(private val context: Context) : parent, false ) - lifecycleOwner = parent.findViewTreeLifecycleOwner() + if (lifecycleOwner == null) { + lifecycleOwner = parent.findViewTreeLifecycleOwner() + } return WgInterfaceViewHolder(itemBinding) } override fun onViewDetachedFromWindow(holder: WgInterfaceViewHolder) { super.onViewDetachedFromWindow(holder) - configs.values.forEach { it.cancel() } - configs.clear() + holder.cancelJobIfAny() } inner class WgInterfaceViewHolder(private val b: ListItemWgGeneralInterfaceBinding) : RecyclerView.ViewHolder(b.root) { + private var job: Job? = null fun update(config: WgConfigFiles) { b.interfaceNameText.text = config.name @@ -119,13 +119,9 @@ class WgConfigAdapter(private val context: Context) : private fun updateStatusJob(config: WgConfigFiles) { if (config.isActive && VpnController.hasTunnel()) { - val job = updateProxyStatusContinuously(config) - if (job != null) { - // cancel the job if it already exists for the same config - cancelJobIfAny(config.id) - configs[config.id] = job - } + job = updateProxyStatusContinuously(config) } else { + cancelJobIfAny() disableInactiveConfig(config) } } @@ -152,8 +148,6 @@ class WgConfigAdapter(private val context: Context) : b.interfaceConfigStatus.text = context.getString(R.string.lbl_disabled).replaceFirstChar(Char::titlecase) } - // cancel the job if it already exists for the config, as the config is disabled - cancelJobIfAny(config.id) } private fun updateProxyStatusContinuously(config: WgConfigFiles): Job? { @@ -199,15 +193,10 @@ class WgConfigAdapter(private val context: Context) : } } - private fun cancelJobIfAny(id: Int) { - val job = configs[id] - job?.cancel() - configs.remove(id) - } - - private fun cancelAllJobs() { - configs.values.forEach { it.cancel() } - configs.clear() + fun cancelJobIfAny() { + if (job?.isActive == true) { + job?.cancel() + } } private suspend fun updateStatus(config: WgConfigFiles) { @@ -232,7 +221,7 @@ class WgConfigAdapter(private val context: Context) : ?.currentState ?.isAtLeast(androidx.lifecycle.Lifecycle.State.STARTED) == false ) { - cancelAllJobs() + cancelJobIfAny() return } uiCtx { @@ -387,7 +376,7 @@ class WgConfigAdapter(private val context: Context) : R.string.symbol_upload, Utilities.humanReadableByteCount(stats.tx, true) ) - return context.getString(R.string.two_argument_space, rx, tx) + return context.getString(R.string.two_argument_space, tx, rx) } private fun getUpTime(stats: Stats?): CharSequence { @@ -455,12 +444,14 @@ class WgConfigAdapter(private val context: Context) : if (WireguardManager.canDisableConfig(cfg)) { WireguardManager.disableConfig(cfg) } else { - Utilities.showToastUiCentered( - context, - context.getString(R.string.wireguard_disable_failure), - Toast.LENGTH_LONG - ) - b.interfaceSwitch.isChecked = true + uiCtx { + Utilities.showToastUiCentered( + context, + context.getString(R.string.wireguard_disable_failure), + Toast.LENGTH_LONG + ) + b.interfaceSwitch.isChecked = true + } } WireguardManager.disableConfig(cfg) From 49369d8fb8284e29c6706393553c36db645d0cdc Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 24 Jun 2024 20:55:05 +0530 Subject: [PATCH 009/188] ui: ensure consistent upload and download order --- .../com/celzero/bravedns/adapter/SummaryStatisticsAdapter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/SummaryStatisticsAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/SummaryStatisticsAdapter.kt index 80214ad68..973653022 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/SummaryStatisticsAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/SummaryStatisticsAdapter.kt @@ -160,7 +160,7 @@ class SummaryStatisticsAdapter( R.string.symbol_upload, Utilities.humanReadableByteCount(appConnection.uploadBytes, true) ) - val total = context.getString(R.string.two_argument, download, upload) + val total = context.getString(R.string.two_argument, upload, download) itemBinding.ssDataUsage.text = total itemBinding.ssCount.text = appConnection.count.toString() } From 751073e4b8351b7b2455e0cfe1b5ee214bc22d41 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 24 Jun 2024 21:00:50 +0530 Subject: [PATCH 010/188] add new 'region' column to dns logs and update ui --- .../ui/bottomsheet/DnsBlocklistBottomSheet.kt | 7 +++++++ app/src/full/res/layout/bottom_sheet_dns_log.xml | 13 ++++++++++++- .../java/com/celzero/bravedns/database/DnsLog.kt | 1 + .../com/celzero/bravedns/database/LogDatabase.kt | 13 ++++++++++++- .../com/celzero/bravedns/net/doh/Transaction.kt | 1 + .../com/celzero/bravedns/service/DnsLogTracker.kt | 2 ++ 6 files changed, 35 insertions(+), 2 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/DnsBlocklistBottomSheet.kt b/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/DnsBlocklistBottomSheet.kt index 40c12109b..41febb513 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/DnsBlocklistBottomSheet.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/DnsBlocklistBottomSheet.kt @@ -134,6 +134,13 @@ class DnsBlocklistBottomSheet : BottomSheetDialogFragment() { displayRecordTypeChip() setupClickListeners() updateRulesUi(log!!.queryStr) + + if (log!!.region.isNotEmpty()) { + b.dnsRegion.visibility = View.VISIBLE + b.dnsRegion.text = log!!.region + } else { + b.dnsRegion.visibility = View.GONE + } } private fun getResponseIp(): String { diff --git a/app/src/full/res/layout/bottom_sheet_dns_log.xml b/app/src/full/res/layout/bottom_sheet_dns_log.xml index ed1019b86..f1f450652 100644 --- a/app/src/full/res/layout/bottom_sheet_dns_log.xml +++ b/app/src/full/res/layout/bottom_sheet_dns_log.xml @@ -177,7 +177,7 @@ android:id="@+id/bsdl_domain_rule_ll" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_below="@id/dns_block_blocked_desc" + android:layout_below="@id/dns_region" android:layout_marginLeft="2dp" android:layout_marginTop="5dp" android:layout_marginRight="2dp" @@ -225,6 +225,17 @@ android:textSize="@dimen/default_font_text_view" android:visibility="visible" /> + Date: Mon, 24 Jun 2024 21:40:10 +0530 Subject: [PATCH 011/188] logger: append timestamp to crash logs --- .../java/com/celzero/bravedns/scheduler/EnhancedBugReport.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/full/java/com/celzero/bravedns/scheduler/EnhancedBugReport.kt b/app/src/full/java/com/celzero/bravedns/scheduler/EnhancedBugReport.kt index 678e5312d..ebfb3fd1e 100644 --- a/app/src/full/java/com/celzero/bravedns/scheduler/EnhancedBugReport.kt +++ b/app/src/full/java/com/celzero/bravedns/scheduler/EnhancedBugReport.kt @@ -20,6 +20,8 @@ import Logger.LOG_TAG_BUG_REPORT import android.content.Context import android.os.Build import androidx.annotation.RequiresApi +import com.celzero.bravedns.util.Constants +import com.celzero.bravedns.util.Utilities import java.io.File import java.io.FileInputStream import java.io.FileNotFoundException @@ -75,7 +77,8 @@ object EnhancedBugReport { Logger.e(LOG_TAG_BUG_REPORT, "file name is null, cannot write logs to file") return } - val l = logs + "\n" // append a new line character + val time = Utilities.convertLongToTime(System.currentTimeMillis(), Constants.TIME_FORMAT_3) + val l = "$time: $logs\n" // append a new line character file.appendText(l, Charset.defaultCharset()) } catch (e: Exception) { Logger.e(LOG_TAG_BUG_REPORT, "err writing logs to file: ${e.message}", e) From e0850a24c98e1fd79ba4baf260010163408e95c2 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Tue, 25 Jun 2024 18:54:37 +0530 Subject: [PATCH 012/188] ui: align IP and domain rules screen layout --- app/src/full/res/layout/list_item_custom_all_ip.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/full/res/layout/list_item_custom_all_ip.xml b/app/src/full/res/layout/list_item_custom_all_ip.xml index e344397c9..a7466c35d 100644 --- a/app/src/full/res/layout/list_item_custom_all_ip.xml +++ b/app/src/full/res/layout/list_item_custom_all_ip.xml @@ -15,7 +15,6 @@ android:id="@+id/custom_ip_app_name_container" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="10dp" android:gravity="center_vertical" android:orientation="horizontal" android:padding="10dp"> @@ -45,6 +44,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/custom_ip_app_name_container" + android:layout_margin="5dp" android:padding="5dp"> Date: Tue, 25 Jun 2024 21:38:22 +0530 Subject: [PATCH 013/188] ui: add links for reddit, element, and mastodon --- .../bravedns/ui/fragment/AboutFragment.kt | 23 +++++++++++++++++++ app/src/main/res/drawable/ic_element.xml | 14 +++++++++++ app/src/main/res/drawable/ic_mastodon.xml | 12 ++++++++++ app/src/main/res/drawable/ic_reddit.xml | 19 +++++++++++++++ app/src/main/res/values/strings.xml | 14 ++++++++++- 5 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/drawable/ic_element.xml create mode 100644 app/src/main/res/drawable/ic_mastodon.xml create mode 100644 app/src/main/res/drawable/ic_reddit.xml diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/AboutFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/AboutFragment.kt index bcb3c3f89..75406be22 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/AboutFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/AboutFragment.kt @@ -55,6 +55,7 @@ import com.celzero.bravedns.scheduler.WorkScheduler import com.celzero.bravedns.service.AppUpdater import com.celzero.bravedns.service.VpnController import com.celzero.bravedns.ui.HomeScreenActivity +import com.celzero.bravedns.ui.activity.ConsoleLogActivity import com.celzero.bravedns.util.Constants.Companion.INIT_TIME_MS import com.celzero.bravedns.util.Constants.Companion.RETHINKDNS_SPONSOR_LINK import com.celzero.bravedns.util.UIUtils.openAppInfo @@ -96,6 +97,16 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener, K b.aboutAppUpdate.visibility = View.GONE } + val followUs = getString(R.string.lbl_follow_us) + b.aboutTwitter.text = + getString(R.string.two_argument_space, followUs, getString(R.string.lbl_twitter)) + b.aboutReddit.text = + getString(R.string.two_argument_space, followUs, getString(R.string.lbl_reddit)) + b.aboutMastodon.text = + getString(R.string.two_argument_space, followUs, getString(R.string.lbl_mastodon)) + b.aboutElement.text = + getString(R.string.two_argument_space, followUs, getString(R.string.lbl_element)) + b.aboutSponsor.setOnClickListener(this) b.aboutWebsite.setOnClickListener(this) b.aboutTwitter.setOnClickListener(this) @@ -104,6 +115,9 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener, K b.aboutPrivacyPolicy.setOnClickListener(this) b.aboutMail.setOnClickListener(this) b.aboutTelegram.setOnClickListener(this) + b.aboutReddit.setOnClickListener(this) + b.aboutMastodon.setOnClickListener(this) + b.aboutElement.setOnClickListener(this) b.aboutFaq.setOnClickListener(this) b.mozillaImg.setOnClickListener(this) b.fossImg.setOnClickListener(this) @@ -217,6 +231,15 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener, K b.aboutPrivacyPolicy -> { openActionViewIntent(getString(R.string.about_privacy_policy_link).toUri()) } + b.aboutReddit -> { + openActionViewIntent(getString(R.string.about_reddit_handle).toUri()) + } + b.aboutMastodon -> { + openActionViewIntent(getString(R.string.about_mastodom_handle).toUri()) + } + b.aboutElement -> { + openActionViewIntent(getString(R.string.about_matrix_handle).toUri()) + } } } diff --git a/app/src/main/res/drawable/ic_element.xml b/app/src/main/res/drawable/ic_element.xml new file mode 100644 index 000000000..bd8ff98de --- /dev/null +++ b/app/src/main/res/drawable/ic_element.xml @@ -0,0 +1,14 @@ + + + diff --git a/app/src/main/res/drawable/ic_mastodon.xml b/app/src/main/res/drawable/ic_mastodon.xml new file mode 100644 index 000000000..b911e16fb --- /dev/null +++ b/app/src/main/res/drawable/ic_mastodon.xml @@ -0,0 +1,12 @@ + + + diff --git a/app/src/main/res/drawable/ic_reddit.xml b/app/src/main/res/drawable/ic_reddit.xml new file mode 100644 index 000000000..e43812e12 --- /dev/null +++ b/app/src/main/res/drawable/ic_reddit.xml @@ -0,0 +1,19 @@ + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f20ac7939..57dbf322c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -100,6 +100,11 @@ IP Unselected notification + follow us on + X + Mastodon + Element + Reddit Rethink is experiencing low memory. System actions may be limited. @@ -789,6 +794,9 @@ https://www.osomprivacy.com https://svc.rethinkdns.com/r/translate https://www.rethinkdns.com/privacy + https://joinmastodom.social + https://www.reddit.com/r/rethinkdns + https://matrix.to/#/!jrTSpJiEkFNNBMhSaE:matrix.org %1$s %1$s (%2$s) Suggest features @@ -1267,7 +1275,7 @@ Connected to %1$s IP address(es) 0 IP address - No network logs for this app. + No network logs for this category. is allowed is blocked @@ -1544,6 +1552,10 @@ Anonymized DNS relay hosted in the US - Los Angeles, CA provided by https://cryptostrom.is/. Anonymized DNS relay hosted in Singapore. + + Application Log + The below logs contains your sensitive information. Please do not share it to any public platform. + Application Icon From 16c9e9494bf25ad9344c5b9bfaca758003447589 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Wed, 26 Jun 2024 22:21:34 +0530 Subject: [PATCH 016/188] fix: #1557, handle proper closure for zip entries --- .../com/celzero/bravedns/scheduler/BugReportZipper.kt | 11 +++++++++-- .../com/celzero/bravedns/scheduler/LogExportWorker.kt | 3 +-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/scheduler/BugReportZipper.kt b/app/src/full/java/com/celzero/bravedns/scheduler/BugReportZipper.kt index 5e0d6bd88..02cfafd1a 100644 --- a/app/src/full/java/com/celzero/bravedns/scheduler/BugReportZipper.kt +++ b/app/src/full/java/com/celzero/bravedns/scheduler/BugReportZipper.kt @@ -228,8 +228,15 @@ object BugReportZipper { } private fun copy(input: InputStream, output: OutputStream) { - while (input.read() != -1) { - output.write(input.readBytes()) + try { + val buffer = ByteArray(DEFAULT_BUFFER_SIZE) + var bytesRead: Int + while (input.read(buffer).also { bytesRead = it } != -1) { + output.write(buffer, 0, bytesRead) + } + output.close() + } catch (e: Exception) { + Logger.w(LOG_TAG_BUG_REPORT, "Exception while copying the file", e) } } } diff --git a/app/src/full/java/com/celzero/bravedns/scheduler/LogExportWorker.kt b/app/src/full/java/com/celzero/bravedns/scheduler/LogExportWorker.kt index 60d8b155d..d64c6c767 100644 --- a/app/src/full/java/com/celzero/bravedns/scheduler/LogExportWorker.kt +++ b/app/src/full/java/com/celzero/bravedns/scheduler/LogExportWorker.kt @@ -46,7 +46,7 @@ class LogExportWorker(context: Context, workerParams: WorkerParameters) : } ZipOutputStream(BufferedOutputStream(FileOutputStream(filePath))).use { zos -> - val zipEntry = ZipEntry("log.txt") + val zipEntry = ZipEntry("log_${System.currentTimeMillis()}.txt") zos.putNextEntry(zipEntry) if (cursor.moveToFirst()) { do { @@ -60,7 +60,6 @@ class LogExportWorker(context: Context, workerParams: WorkerParameters) : } zos.closeEntry() } - cursor.close() Logger.i(LOG_TAG_BUG_REPORT, "Logs exported to ${file.absolutePath}") return true From 0ea571ddec0bd0137c55d722ca6a3c881742df42 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Wed, 26 Jun 2024 22:23:24 +0530 Subject: [PATCH 017/188] ui: display wireguard id along with the name --- .../bravedns/adapter/OneWgConfigAdapter.kt | 27 +++++++--- .../bravedns/adapter/WgConfigAdapter.kt | 50 ++++++++++--------- 2 files changed, 46 insertions(+), 31 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt index c7763ea28..f4876fb34 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt @@ -116,7 +116,13 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns private var job: Job? = null fun update(config: WgConfigFiles) { - b.interfaceNameText.text = config.name + // limit the config name to 12 characters + b.interfaceNameText.text = + context.getString( + R.string.about_version_install_source, + config.name.take(11), + config.id.toString() + ) val isWgActive = config.isActive && VpnController.hasTunnel() b.oneWgCheck.isChecked = isWgActive setupClickListeners(config) @@ -226,9 +232,10 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns b.interfaceDetailCard.strokeColor = fetchColor(context, R.attr.accentGood) } - } else if (statusId == Backend.TUP || - statusId == Backend.TZZ || - statusId == Backend.TNT + } else if ( + statusId == Backend.TUP || + statusId == Backend.TZZ || + statusId == Backend.TNT ) { b.interfaceDetailCard.strokeColor = fetchColor(context, R.attr.chipTextNeutral) @@ -365,7 +372,8 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns uiCtx { Utilities.showToastUiCentered( context, - ERR_CODE_VPN_NOT_ACTIVE + context.getString(R.string.settings_socks5_vpn_disabled_error), + ERR_CODE_VPN_NOT_ACTIVE + + context.getString(R.string.settings_socks5_vpn_disabled_error), Toast.LENGTH_LONG ) // reset the check box @@ -381,7 +389,8 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns b.oneWgCheck.isChecked = false Utilities.showToastUiCentered( context, - ERR_CODE_VPN_NOT_FULL + context.getString(R.string.wireguard_enabled_failure), + ERR_CODE_VPN_NOT_FULL + + context.getString(R.string.wireguard_enabled_failure), Toast.LENGTH_LONG ) } @@ -395,7 +404,8 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns b.oneWgCheck.isChecked = false Utilities.showToastUiCentered( context, - ERR_CODE_OTHER_WG_ACTIVE + context.getString(R.string.wireguard_enabled_failure), + ERR_CODE_OTHER_WG_ACTIVE + + context.getString(R.string.wireguard_enabled_failure), Toast.LENGTH_LONG ) } @@ -431,7 +441,8 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns b.oneWgCheck.isChecked = true Utilities.showToastUiCentered( context, - ERR_CODE_VPN_NOT_ACTIVE + context.getString(R.string.settings_socks5_vpn_disabled_error), + ERR_CODE_VPN_NOT_ACTIVE + + context.getString(R.string.settings_socks5_vpn_disabled_error), Toast.LENGTH_LONG ) } diff --git a/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt index aee09413a..8f9293660 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt @@ -74,10 +74,10 @@ class WgConfigAdapter(private val context: Context) : newConnection: WgConfigFiles ): Boolean { return (oldConnection.id == newConnection.id && - oldConnection.name == newConnection.name && - oldConnection.isActive == newConnection.isActive && - oldConnection.isCatchAll == newConnection.isCatchAll && - oldConnection.isLockdown == newConnection.isLockdown) + oldConnection.name == newConnection.name && + oldConnection.isActive == newConnection.isActive && + oldConnection.isCatchAll == newConnection.isCatchAll && + oldConnection.isLockdown == newConnection.isLockdown) } } } @@ -111,7 +111,12 @@ class WgConfigAdapter(private val context: Context) : private var job: Job? = null fun update(config: WgConfigFiles) { - b.interfaceNameText.text = config.name + b.interfaceNameText.text = + context.getString( + R.string.about_version_install_source, + config.name.take(11), + config.id.toString() + ) b.interfaceSwitch.isChecked = config.isActive && VpnController.hasTunnel() setupClickListeners(config) updateStatusJob(config) @@ -216,10 +221,10 @@ class WgConfigAdapter(private val context: Context) : // if the view is not active then cancel the job if ( lifecycleOwner != null && - lifecycleOwner - ?.lifecycle - ?.currentState - ?.isAtLeast(androidx.lifecycle.Lifecycle.State.STARTED) == false + lifecycleOwner + ?.lifecycle + ?.currentState + ?.isAtLeast(androidx.lifecycle.Lifecycle.State.STARTED) == false ) { cancelJobIfAny() return @@ -311,8 +316,8 @@ class WgConfigAdapter(private val context: Context) : } } else if ( statusId == Backend.TUP || - statusId == Backend.TZZ || - statusId == Backend.TNT + statusId == Backend.TZZ || + statusId == Backend.TNT ) { b.interfaceDetailCard.strokeColor = UIUtils.fetchColor(context, R.attr.chipTextNeutral) @@ -334,8 +339,8 @@ class WgConfigAdapter(private val context: Context) : // for idle state, if lastOk is less than 30 sec, then show as connected if ( stats.lastOK != 0L && - System.currentTimeMillis() - stats.lastOK < - 30 * DateUtils.SECOND_IN_MILLIS + System.currentTimeMillis() - stats.lastOK < + 30 * DateUtils.SECOND_IN_MILLIS ) { status = context @@ -432,7 +437,8 @@ class WgConfigAdapter(private val context: Context) : uiCtx { Utilities.showToastUiCentered( context, - ERR_CODE_VPN_NOT_ACTIVE + context.getString(R.string.settings_socks5_vpn_disabled_error), + ERR_CODE_VPN_NOT_ACTIVE + + context.getString(R.string.settings_socks5_vpn_disabled_error), Toast.LENGTH_LONG ) // reset the check box @@ -464,7 +470,8 @@ class WgConfigAdapter(private val context: Context) : uiCtx { Utilities.showToastUiCentered( context, - ERR_CODE_VPN_NOT_ACTIVE + context.getString(R.string.settings_socks5_vpn_disabled_error), + ERR_CODE_VPN_NOT_ACTIVE + + context.getString(R.string.settings_socks5_vpn_disabled_error), Toast.LENGTH_LONG ) // reset the check box @@ -474,16 +481,14 @@ class WgConfigAdapter(private val context: Context) : } if (!WireguardManager.canEnableProxy()) { - Logger.i( - LOG_TAG_PROXY, - "not in DNS+Firewall mode, cannot enable WireGuard" - ) + Logger.i(LOG_TAG_PROXY, "not in DNS+Firewall mode, cannot enable WireGuard") uiCtx { // reset the check box b.interfaceSwitch.isChecked = false Utilities.showToastUiCentered( context, - ERR_CODE_VPN_NOT_FULL + context.getString(R.string.wireguard_enabled_failure), + ERR_CODE_VPN_NOT_FULL + + context.getString(R.string.wireguard_enabled_failure), Toast.LENGTH_LONG ) } @@ -498,9 +503,8 @@ class WgConfigAdapter(private val context: Context) : b.interfaceSwitch.isChecked = false Utilities.showToastUiCentered( context, - ERR_CODE_OTHER_WG_ACTIVE + context.getString( - R.string.wireguard_enabled_failure - ), + ERR_CODE_OTHER_WG_ACTIVE + + context.getString(R.string.wireguard_enabled_failure), Toast.LENGTH_LONG ) } From 7f2e219b6183b4938b7ad82710172e83003bac62 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Wed, 26 Jun 2024 22:24:27 +0530 Subject: [PATCH 018/188] strings: rmv unused literal from all xml files --- app/src/main/res/values-hi/strings.xml | 1 - app/src/main/res/values-iw/strings.xml | 1 - app/src/main/res/values-ja/strings.xml | 1 - app/src/main/res/values-ko/strings.xml | 1 - app/src/main/res/values-pl/strings.xml | 1 - app/src/main/res/values-ta/strings.xml | 1 - app/src/main/res/values-vi/strings.xml | 1 - app/src/main/res/values/strings.xml | 1 - 8 files changed, 8 deletions(-) diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 75065b941..b96af12a0 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -371,7 +371,6 @@ ऑन-डिवाइस ब्लॉकलिस्ट गोपनीयता नीति पढ़े ऐप जानकारी - ट्वीटर पर हमें फोलो करे अनुवाद करे इस ईमेल को यहां भेजें: hello@celzero.com %1$s लॉन्च नहीं हुए diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index a38bb04f1..e17b6a8fa 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -199,7 +199,6 @@ התחל מה חדש פרוקסי פעיל - עקבו אחרינו בטוויטר למחוק הכל RethinkDNS היא חלק מתוכנית ה-MVP של Mozilla Builders. לא ניתן היה להפעיל את פרופיל VPN. diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 013ec284d..1c892de5b 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -218,7 +218,6 @@ VPNプロファイル 通知設定 %1$s の新機能 - Twitterでフォローしてください RethinkDNSは、Mozilla Builders MVPプログラムの一部です。 更新を確認する 開発者 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 3329e8252..9bdc70444 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -290,7 +290,6 @@ 연락처 자주 묻는 질문 보기 Rethink는 Mozilla Builders, FOSS United Foundation, OSOM Products Inc.가 지원합니다. - Twitter에서 우리를 팔로우 하기 %1$s에서 새로운 것들 앱 업데이트 확인하기 저자 diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 2e4ee44e4..eb82ff50f 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -514,7 +514,6 @@ Nie można pobrać logów awarii (błędów). Czy chcesz ręcznie zgłosić swój błąd\? Wyślij e-mail Odwiedź stronę rethinkdns.com - Dołącz do nas na Twitterze Wesprzyj nas na Github Zobacz FAQs v%1$s (%2$s) diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index 5c0767934..64c2465cf 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -624,7 +624,6 @@ மண்டலம் பயன்பாட்டு செய்தி VPN சுயவிவரம் - Twitter இல் எங்களை பின்தொடரவும் பயன்பாட்டு புதுப்பிப்புகளைச் சரிபார்க்கவும் மின்னஞ்சல் வழங்குநரைத் தேர்ந்தெடுக்கவும் அம்சங்களை பரிந்துரைக்கவும் diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index d4d3f070a..2ee7f8d4d 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -381,7 +381,6 @@ Chủ đề: %1$s DNS khác Hiển thị biểu tượng trong nhật ký DNS (thử nghiệm) - Theo dõi chúng tôi trên Twitter Kiểm tra các bản cập nhật ứng dụng Đề xuất các tính năng Không thể khởi chạy cấu hình VPN. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 95399abc9..04ce2cb62 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -775,7 +775,6 @@ VPN Profile Notification Settings What\'s new in %1$s - Follow us on Twitter Rethink is supported by Mozilla Builders, FOSS United Foundation, OSOM Products Inc. Check for app updates Authors From 1d51bef5b0a27780037fdf6feaa53f0cdbee133f Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Wed, 26 Jun 2024 22:25:51 +0530 Subject: [PATCH 019/188] logger: move in-memory writes to separate method --- .../java/com/celzero/bravedns/util/Logger.kt | 24 +++++++-- .../java/com/celzero/bravedns/util/Logger.kt | 20 ++++++- .../java/com/celzero/bravedns/util/Logger.kt | 54 ++++++++++++++----- 3 files changed, 79 insertions(+), 19 deletions(-) diff --git a/app/src/fdroid/java/com/celzero/bravedns/util/Logger.kt b/app/src/fdroid/java/com/celzero/bravedns/util/Logger.kt index 9e5c108b7..30ddf8f19 100644 --- a/app/src/fdroid/java/com/celzero/bravedns/util/Logger.kt +++ b/app/src/fdroid/java/com/celzero/bravedns/util/Logger.kt @@ -21,6 +21,7 @@ import org.koin.core.component.inject object Logger : KoinComponent { private val persistentState by inject() + private val inMemDb by inject() private var logLevel = persistentState.goLoggerLevel const val LOG_TAG_APP_UPDATE = "NonStoreAppUpdater" @@ -111,11 +112,11 @@ object Logger : KoinComponent { } fun enableCrashlytics() { - // no-op, crashlytics is not used for F-Droid builds + // no-op, crashlytics is not used for F-Droid variant } fun disableCrashlytics() { - // no-op, crashlytics is not used for F-Droid builds + // no-op, crashlytics is not used for F-Droid variant } fun updateConfigLevel(level: Long) { @@ -130,7 +131,12 @@ object Logger : KoinComponent { } } - private fun log(tag: String, msg: String, type: LoggerType, e: Exception? = null) { + fun goLog(message: String, type: LoggerType) { + // no need to log the go logs, add it to the database + dbWrite("", message, type) + } + + fun log(tag: String, msg: String, type: LoggerType, e: Exception? = null) { when (type) { LoggerType.VERY_VERBOSE -> if (logLevel <= LoggerType.VERY_VERBOSE.id) Log.v(tag, msg) LoggerType.VERBOSE -> if (logLevel <= LoggerType.VERBOSE.id) Log.v(tag, msg) @@ -142,6 +148,10 @@ object Logger : KoinComponent { LoggerType.USR -> {} // Do nothing LoggerType.NONE -> {} // Do nothing } + dbWrite(tag, msg, type, e) + } + + private fun dbWrite(tag: String, msg: String, type: LoggerType, e: Exception? = null) { if (type.id >= logLevel) { // get the first letter of the level and append it to the tag val l = when (type) { @@ -153,7 +163,13 @@ object Logger : KoinComponent { LoggerType.STACKTRACE -> "E" else -> "V" } - VpnController.writeConsoleLog("$l $tag: $msg") + val log = if (tag.isEmpty()) { + ConsoleLog(0, msg, System.currentTimeMillis()) + } else { + ConsoleLog(0, "$l $tag: $msg", System.currentTimeMillis()) + } + // insert the log to the database via channel + inMemDb.logChannel.trySend(log) } } } diff --git a/app/src/play/java/com/celzero/bravedns/util/Logger.kt b/app/src/play/java/com/celzero/bravedns/util/Logger.kt index bbd880996..0f26a6b43 100644 --- a/app/src/play/java/com/celzero/bravedns/util/Logger.kt +++ b/app/src/play/java/com/celzero/bravedns/util/Logger.kt @@ -23,6 +23,7 @@ import org.koin.core.component.inject object Logger : KoinComponent { private val persistentState by inject() + private val inMemDb by inject() private var logLevel = persistentState.goLoggerLevel const val LOG_TAG_APP_UPDATE = "NonStoreAppUpdater" @@ -148,7 +149,12 @@ object Logger : KoinComponent { } } - private fun log(tag: String, msg: String, type: LoggerType, e: Exception? = null) { + fun goLog(message: String, type: LoggerType) { + // no need to log the go logs, add it to the database + dbWrite("", message, type) + } + + fun log(tag: String, msg: String, type: LoggerType, e: Exception? = null) { when (type) { LoggerType.VERY_VERBOSE -> if (logLevel <= LoggerType.VERY_VERBOSE.id) Log.v(tag, msg) LoggerType.VERBOSE -> if (logLevel <= LoggerType.VERBOSE.id) Log.v(tag, msg) @@ -160,6 +166,10 @@ object Logger : KoinComponent { LoggerType.USR -> {} // Do nothing LoggerType.NONE -> {} // Do nothing } + dbWrite(tag, msg, type, e) + } + + private fun dbWrite(tag: String, msg: String, type: LoggerType, e: Exception? = null) { if (type.id >= logLevel) { // get the first letter of the level and append it to the tag val l = when (type) { @@ -171,7 +181,13 @@ object Logger : KoinComponent { LoggerType.STACKTRACE -> "E" else -> "V" } - VpnController.writeConsoleLog("$l $tag: $msg") + val log = if (tag.isEmpty()) { + ConsoleLog(0, msg, System.currentTimeMillis()) + } else { + ConsoleLog(0, "$l $tag: $msg", System.currentTimeMillis()) + } + // insert the log to the database via channel + inMemDb.logChannel.trySend(log) } } } diff --git a/app/src/website/java/com/celzero/bravedns/util/Logger.kt b/app/src/website/java/com/celzero/bravedns/util/Logger.kt index e95a8bbdd..cf7ed2814 100644 --- a/app/src/website/java/com/celzero/bravedns/util/Logger.kt +++ b/app/src/website/java/com/celzero/bravedns/util/Logger.kt @@ -14,13 +14,15 @@ * limitations under the License. */ import android.util.Log +import com.celzero.bravedns.database.ConsoleLog +import com.celzero.bravedns.database.ConsoleLogRepository import com.celzero.bravedns.service.PersistentState -import com.celzero.bravedns.service.VpnController import org.koin.core.component.KoinComponent import org.koin.core.component.inject object Logger : KoinComponent { private val persistentState by inject() + private val inMemDb by inject() private var logLevel = persistentState.goLoggerLevel const val LOG_TAG_APP_UPDATE = "NonStoreAppUpdater" @@ -130,7 +132,12 @@ object Logger : KoinComponent { } } - private fun log(tag: String, msg: String, type: LoggerType, e: Exception? = null) { + fun goLog(message: String, type: LoggerType) { + // no need to log the go logs, add it to the database + dbWrite("", message, type) + } + + fun log(tag: String, msg: String, type: LoggerType, e: Exception? = null) { when (type) { LoggerType.VERY_VERBOSE -> if (logLevel <= LoggerType.VERY_VERBOSE.id) Log.v(tag, msg) LoggerType.VERBOSE -> if (logLevel <= LoggerType.VERBOSE.id) Log.v(tag, msg) @@ -142,18 +149,39 @@ object Logger : KoinComponent { LoggerType.USR -> {} // Do nothing LoggerType.NONE -> {} // Do nothing } - if (type.id >= logLevel) { - // get the first letter of the level and append it to the tag - val l = when (type) { - LoggerType.VERBOSE -> "V" - LoggerType.DEBUG -> "D" - LoggerType.INFO -> "I" - LoggerType.WARN -> "W" - LoggerType.ERROR -> "E" - LoggerType.STACKTRACE -> "E" - else -> "V" + dbWrite(tag, msg, type, e) + } + + private fun dbWrite(tag: String, msg: String, type: LoggerType, e: Exception? = null) { + try { + if (tag.isEmpty()) { + val log = ConsoleLog(0, msg, System.currentTimeMillis()) + inMemDb.logChannel.trySend(log) + } else if (type.id >= logLevel) { + val l = when (type) { + LoggerType.VERBOSE -> "V" + LoggerType.DEBUG -> "D" + LoggerType.INFO -> "I" + LoggerType.WARN -> "W" + LoggerType.ERROR -> "E" + LoggerType.STACKTRACE -> "E" + else -> "V" + } + val log = if (e != null) { + ConsoleLog( + 0, + "$l $tag: $msg\n${Log.getStackTraceString(e)}", + System.currentTimeMillis() + ) + } else { + ConsoleLog(0, "$l $tag: $msg", System.currentTimeMillis()) + } + inMemDb.logChannel.trySend(log) + } else { + // Do nothing } - VpnController.writeConsoleLog("$l $tag: $msg") + } catch (ex: Exception) { + Log.e(LOG_GO_LOGGER, "err while writing log to the database: ${ex.message}", ex) } } } From 905d4c80595234cf894b15c7edc0fb9157be89ee Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 27 Jun 2024 19:44:51 +0530 Subject: [PATCH 020/188] string: fix contacts in about screen --- .../celzero/bravedns/ui/fragment/AboutFragment.kt | 14 -------------- app/src/main/res/layout/fragment_about.xml | 6 ++++-- app/src/main/res/values-hi/strings.xml | 1 + app/src/main/res/values-iw/strings.xml | 1 + app/src/main/res/values-ja/strings.xml | 1 + app/src/main/res/values-ko/strings.xml | 1 + app/src/main/res/values-pl/strings.xml | 1 + app/src/main/res/values-ta/strings.xml | 1 + app/src/main/res/values-vi/strings.xml | 1 + app/src/main/res/values/strings.xml | 4 ++-- 10 files changed, 13 insertions(+), 18 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/AboutFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/AboutFragment.kt index 75406be22..18acefe0c 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/AboutFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/AboutFragment.kt @@ -22,10 +22,8 @@ import android.content.DialogInterface import android.content.Intent import android.content.pm.PackageInfo import android.content.pm.PackageManager -import android.icu.lang.UCharacter.GraphemeClusterBreak.T import android.net.Uri import android.os.Bundle -import android.os.Parcelable import android.os.SystemClock import android.provider.Settings import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS @@ -53,9 +51,7 @@ import com.celzero.bravedns.scheduler.BugReportZipper.getZipFileName import com.celzero.bravedns.scheduler.EnhancedBugReport import com.celzero.bravedns.scheduler.WorkScheduler import com.celzero.bravedns.service.AppUpdater -import com.celzero.bravedns.service.VpnController import com.celzero.bravedns.ui.HomeScreenActivity -import com.celzero.bravedns.ui.activity.ConsoleLogActivity import com.celzero.bravedns.util.Constants.Companion.INIT_TIME_MS import com.celzero.bravedns.util.Constants.Companion.RETHINKDNS_SPONSOR_LINK import com.celzero.bravedns.util.UIUtils.openAppInfo @@ -97,16 +93,6 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener, K b.aboutAppUpdate.visibility = View.GONE } - val followUs = getString(R.string.lbl_follow_us) - b.aboutTwitter.text = - getString(R.string.two_argument_space, followUs, getString(R.string.lbl_twitter)) - b.aboutReddit.text = - getString(R.string.two_argument_space, followUs, getString(R.string.lbl_reddit)) - b.aboutMastodon.text = - getString(R.string.two_argument_space, followUs, getString(R.string.lbl_mastodon)) - b.aboutElement.text = - getString(R.string.two_argument_space, followUs, getString(R.string.lbl_element)) - b.aboutSponsor.setOnClickListener(this) b.aboutWebsite.setOnClickListener(this) b.aboutTwitter.setOnClickListener(this) diff --git a/app/src/main/res/layout/fragment_about.xml b/app/src/main/res/layout/fragment_about.xml index 6e9aced80..3b52f8fa5 100644 --- a/app/src/main/res/layout/fragment_about.xml +++ b/app/src/main/res/layout/fragment_about.xml @@ -477,13 +477,12 @@ android:background="?android:attr/selectableItemBackground" android:drawableStart="@drawable/ic_twitter" android:drawablePadding="20dp" + android:text="@string/about_twitter" android:gravity="start|center_vertical" android:lineSpacingExtra="5dp" android:padding="5dp" - android:text="@string/about_twitter" android:textSize="@dimen/large_font_text_view" /> - @@ -527,6 +528,7 @@ android:background="?android:attr/selectableItemBackground" android:drawableStart="@drawable/ic_element" android:drawablePadding="20dp" + android:text="@string/lbl_matrix" android:gravity="start|center_vertical" android:lineSpacingExtra="5dp" android:padding="5dp" diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index b96af12a0..8bf60c53a 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -372,6 +372,7 @@ गोपनीयता नीति पढ़े ऐप जानकारी अनुवाद करे + ट्वीटर पर हमें फोलो करे इस ईमेल को यहां भेजें: hello@celzero.com %1$s लॉन्च नहीं हुए कोई जवाब नहीं diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index e17b6a8fa..6df243fc2 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -200,6 +200,7 @@ מה חדש פרוקסי פעיל למחוק הכל + עקבו אחרינו בטוויטר RethinkDNS היא חלק מתוכנית ה-MVP של Mozilla Builders. לא ניתן היה להפעיל את פרופיל VPN. חיצוני diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 1c892de5b..013ec284d 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -218,6 +218,7 @@ VPNプロファイル 通知設定 %1$s の新機能 + Twitterでフォローしてください RethinkDNSは、Mozilla Builders MVPプログラムの一部です。 更新を確認する 開発者 diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 9bdc70444..3329e8252 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -290,6 +290,7 @@ 연락처 자주 묻는 질문 보기 Rethink는 Mozilla Builders, FOSS United Foundation, OSOM Products Inc.가 지원합니다. + Twitter에서 우리를 팔로우 하기 %1$s에서 새로운 것들 앱 업데이트 확인하기 저자 diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index eb82ff50f..2e4ee44e4 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -514,6 +514,7 @@ Nie można pobrać logów awarii (błędów). Czy chcesz ręcznie zgłosić swój błąd\? Wyślij e-mail Odwiedź stronę rethinkdns.com + Dołącz do nas na Twitterze Wesprzyj nas na Github Zobacz FAQs v%1$s (%2$s) diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index 64c2465cf..5c0767934 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -624,6 +624,7 @@ மண்டலம் பயன்பாட்டு செய்தி VPN சுயவிவரம் + Twitter இல் எங்களை பின்தொடரவும் பயன்பாட்டு புதுப்பிப்புகளைச் சரிபார்க்கவும் மின்னஞ்சல் வழங்குநரைத் தேர்ந்தெடுக்கவும் அம்சங்களை பரிந்துரைக்கவும் diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 2ee7f8d4d..d4d3f070a 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -381,6 +381,7 @@ Chủ đề: %1$s DNS khác Hiển thị biểu tượng trong nhật ký DNS (thử nghiệm) + Theo dõi chúng tôi trên Twitter Kiểm tra các bản cập nhật ứng dụng Đề xuất các tính năng Không thể khởi chạy cấu hình VPN. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 04ce2cb62..b66f972a4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -100,10 +100,9 @@ IP Unselected notification - follow us on X Mastodon - Element + Matrix Reddit test @@ -775,6 +774,7 @@ VPN Profile Notification Settings What\'s new in %1$s + Follow us on Twitter Rethink is supported by Mozilla Builders, FOSS United Foundation, OSOM Products Inc. Check for app updates Authors From e277466a3f447a68cf7feb09bbddc259c6d1a613 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 27 Jun 2024 19:45:24 +0530 Subject: [PATCH 021/188] server: update server ips for sky, zero --- app/src/main/res/values/servers.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values/servers.xml b/app/src/main/res/values/servers.xml index d690c3527..08f6226e8 100644 --- a/app/src/main/res/values/servers.xml +++ b/app/src/main/res/values/servers.xml @@ -5,11 +5,11 @@ https://basic.rethinkdns.com/ 172.67.162.27,104.21.10.13 https://sky.rethinkdns.com/ - 104.21.10.13,172.67.162.27,2606:4700:3033::ac43:a21b + 172.67.214.246,104.21.83.62,2606:4700:3030::ac43:d6f6,2606:4700:3030::6815:533e https://max.rethinkdns.com/ 137.66.7.89,2a09:8280:1::1:7432 https://zero.rethinkdns.com/ - 104.21.10.13,172.67.162.27 + 104.21.83.62,172.67.214.246,2606:4700:3030::6815:533e,2606:4700:3030::ac43:d6f6 https://dns.google/dns-query 8.8.8.8,8.8.4.4,2001:4860:4860::8888,2001:4860:4860::8844 https://cloudflare-dns.com/dns-query From c31981548eccca92d48cd6aeed725f8c42b04310 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sun, 30 Jun 2024 01:47:56 +0530 Subject: [PATCH 022/188] ui: show rethink app's details in app info screen --- .../bravedns/adapter/AppWiseDomainsAdapter.kt | 10 +- .../bravedns/adapter/AppWiseIpsAdapter.kt | 6 +- .../bravedns/adapter/CustomDomainAdapter.kt | 16 +++ .../bravedns/adapter/CustomIpAdapter.kt | 15 +++ .../bravedns/ui/activity/AppInfoActivity.kt | 63 +++++++++- .../ui/activity/AppWiseDomainLogsActivity.kt | 94 +++++++++++--- .../ui/activity/AppWiseIpLogsActivity.kt | 115 ++++++++++++++---- .../viewmodel/AppConnectionsViewModel.kt | 72 ++++++++++- .../bravedns/database/ConnectionTracker.kt | 1 + .../celzero/bravedns/database/RethinkLog.kt | 1 + .../bravedns/database/RethinkLogDao.kt | 47 ++++++- .../bravedns/database/RethinkLogRepository.kt | 5 + 12 files changed, 386 insertions(+), 59 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/AppWiseDomainsAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/AppWiseDomainsAdapter.kt index ce5fe747e..aae25c9ed 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/AppWiseDomainsAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/AppWiseDomainsAdapter.kt @@ -40,7 +40,8 @@ import kotlin.math.log2 class AppWiseDomainsAdapter( val context: Context, val lifecycleOwner: LifecycleOwner, - val uid: Int + val uid: Int, + val isRethink: Boolean ) : PagingDataAdapter( DIFF_CALLBACK @@ -68,9 +69,6 @@ class AppWiseDomainsAdapter( private lateinit var adapter: AppWiseDomainsAdapter - // ui component to update/toggle the buttons - data class ToggleBtnUi(val txtColor: Int, val bgColor: Int) - override fun onCreateViewHolder( parent: ViewGroup, viewType: Int @@ -145,6 +143,10 @@ class AppWiseDomainsAdapter( return } + if (isRethink) { + return + } + val bottomSheetFragment = AppDomainRulesBottomSheet() // Fix: free-form window crash // all BottomSheetDialogFragment classes created must have a public, no-arg constructor. diff --git a/app/src/full/java/com/celzero/bravedns/adapter/AppWiseIpsAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/AppWiseIpsAdapter.kt index 95f3c86d8..dbf0bf01e 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/AppWiseIpsAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/AppWiseIpsAdapter.kt @@ -37,7 +37,7 @@ import com.celzero.bravedns.util.Utilities import com.celzero.bravedns.util.Utilities.removeBeginningTrailingCommas import kotlin.math.log2 -class AppWiseIpsAdapter(val context: Context, val lifecycleOwner: LifecycleOwner, val uid: Int) : +class AppWiseIpsAdapter(val context: Context, val lifecycleOwner: LifecycleOwner, val uid: Int, val isRethink: Boolean) : PagingDataAdapter(DIFF_CALLBACK), AppIpRulesBottomSheet.OnBottomSheetDialogFragmentDismiss { @@ -112,6 +112,10 @@ class AppWiseIpsAdapter(val context: Context, val lifecycleOwner: LifecycleOwner return } + if (isRethink) { + return + } + val bottomSheetFragment = AppIpRulesBottomSheet() // Fix: free-form window crash // all BottomSheetDialogFragment classes created must have a public, no-arg constructor. diff --git a/app/src/full/java/com/celzero/bravedns/adapter/CustomDomainAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/CustomDomainAdapter.kt index a8deb8cde..3abd6f0bb 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/CustomDomainAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/CustomDomainAdapter.kt @@ -18,6 +18,7 @@ package com.celzero.bravedns.adapter import Logger import Logger.LOG_TAG_UI import android.content.Context +import android.content.Intent import android.content.res.ColorStateList import android.graphics.drawable.Drawable import android.text.format.DateUtils @@ -43,6 +44,8 @@ import com.celzero.bravedns.service.DomainRulesManager import com.celzero.bravedns.service.DomainRulesManager.isValidDomain import com.celzero.bravedns.service.DomainRulesManager.isWildCardEntry import com.celzero.bravedns.service.FirewallManager +import com.celzero.bravedns.ui.activity.AppInfoActivity +import com.celzero.bravedns.ui.activity.AppWiseDomainLogsActivity import com.celzero.bravedns.ui.activity.CustomRulesActivity import com.celzero.bravedns.util.Constants import com.celzero.bravedns.util.UIUtils.fetchColor @@ -420,10 +423,23 @@ class CustomDomainAdapter(val context: Context, val rule: CustomRulesActivity.RU b.customDomainExpandIcon.setOnClickListener { toggleActionsUi() } b.customDomainContainer.setOnClickListener { toggleActionsUi() } + + b.customDomainSeeMoreChip.setOnClickListener { openAppWiseRulesActivity(cd.uid) } } } } + private fun openAppWiseRulesActivity(uid: Int) { + val intent = Intent(context, CustomRulesActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + intent.putExtra( + Constants.VIEW_PAGER_SCREEN_TO_LOAD, + CustomRulesActivity.Tabs.DOMAIN_RULES.screen + ) + intent.putExtra(Constants.INTENT_UID, uid) + context.startActivity(intent) + } + private fun getAppName(uid: Int, appNames: List): String { if (uid == Constants.UID_EVERYBODY) { return context diff --git a/app/src/full/java/com/celzero/bravedns/adapter/CustomIpAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/CustomIpAdapter.kt index 8e748bd97..a18e69b6b 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/CustomIpAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/CustomIpAdapter.kt @@ -18,6 +18,7 @@ package com.celzero.bravedns.adapter import Logger import Logger.LOG_TAG_UI import android.content.Context +import android.content.Intent import android.content.res.ColorStateList import android.graphics.drawable.Drawable import android.text.format.DateUtils @@ -43,6 +44,7 @@ import com.celzero.bravedns.databinding.ListItemCustomIpBinding import com.celzero.bravedns.service.FirewallManager import com.celzero.bravedns.service.IpRulesManager import com.celzero.bravedns.ui.activity.CustomRulesActivity +import com.celzero.bravedns.util.Constants import com.celzero.bravedns.util.Constants.Companion.UID_EVERYBODY import com.celzero.bravedns.util.UIUtils.fetchColor import com.celzero.bravedns.util.UIUtils.fetchToggleBtnColors @@ -278,6 +280,19 @@ class CustomIpAdapter(private val context: Context, private val type: CustomRule b.customIpExpandIcon.setOnClickListener { toggleActionsUi() } b.customIpContainer.setOnClickListener { toggleActionsUi() } + + b.customIpSeeMoreChip.setOnClickListener { openAppWiseRulesActivity(customIp.uid) } + } + + private fun openAppWiseRulesActivity(uid: Int) { + val intent = Intent(context, CustomRulesActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + intent.putExtra( + Constants.VIEW_PAGER_SCREEN_TO_LOAD, + CustomRulesActivity.Tabs.IP_RULES.screen + ) + intent.putExtra(Constants.INTENT_UID, uid) + context.startActivity(intent) } private fun getAppName(uid: Int, appNames: List): String { diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/AppInfoActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/AppInfoActivity.kt index f57a03d34..80c04c5b0 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/AppInfoActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/AppInfoActivity.kt @@ -80,6 +80,8 @@ class AppInfoActivity : AppCompatActivity(R.layout.activity_app_details) { private var showBypassToolTip: Boolean = true + private var isRethinkApp: Boolean = false + companion object { const val UID_INTENT_NAME = "UID" } @@ -121,6 +123,7 @@ class AppInfoActivity : AppCompatActivity(R.layout.activity_app_details) { this.appInfo = appInfo b.aadAppDetailName.text = appName(packages.count()) + b.aadPkgName.text = appInfo.packageName b.excludeProxySwitch.isChecked = appInfo.isProxyExcluded updateDataUsage() displayIcon( @@ -130,15 +133,19 @@ class AppInfoActivity : AppCompatActivity(R.layout.activity_app_details) { // do not show the firewall status if the app is Rethink if (appInfo.packageName == rethinkPkgName) { + isRethinkApp = true b.aadFirewallStatus.visibility = View.GONE hideFirewallStatusUi() hideDomainBlockUi() hideIpBlockUi() - return@uiCtx + hideBypassProxyUi() + setRethinkDomainLogsAdapter() + setRethinkIpLogsAdapter() + } else { + updateFirewallStatusUi(appStatus, connStatus) + setDomainsAdapter() + setIpAdapter() } - updateFirewallStatusUi(appStatus, connStatus) - setDomainsAdapter() - setIpAdapter() } } } @@ -147,6 +154,10 @@ class AppInfoActivity : AppCompatActivity(R.layout.activity_app_details) { b.aadAppSettingsCard.visibility = View.GONE } + private fun hideBypassProxyUi() { + b.excludeProxyRl.visibility = View.GONE + } + private fun hideDomainBlockUi() { b.aadDomainBlockCard.visibility = View.GONE } @@ -372,7 +383,7 @@ class AppInfoActivity : AppCompatActivity(R.layout.activity_app_details) { private fun setDomainsAdapter() { val layoutManager = LinearLayoutManager(this) b.aadMostContactedDomainRv.layoutManager = layoutManager - val adapter = AppWiseDomainsAdapter(this, this, uid) + val adapter = AppWiseDomainsAdapter(this, this, uid, isRethinkApp) networkLogsViewModel.getDomainLogsLimited(uid).observe(this) { adapter.submitData(this.lifecycle, it) } @@ -390,11 +401,51 @@ class AppInfoActivity : AppCompatActivity(R.layout.activity_app_details) { } } + private fun setRethinkDomainLogsAdapter() { + val layoutManager = LinearLayoutManager(this) + b.aadMostContactedDomainRv.layoutManager = layoutManager + val adapter = AppWiseDomainsAdapter(this, this, uid, isRethinkApp) + networkLogsViewModel.getRethinkDomainLogsLimited().observe(this) { + adapter.submitData(this.lifecycle, it) + } + b.aadMostContactedDomainRv.adapter = adapter + + adapter.addLoadStateListener { + if (it.append.endOfPaginationReached) { + if (adapter.itemCount < 1) { + b.aadMostContactedDomainRl.visibility = View.GONE + } else { + b.aadMostContactedDomainRl.visibility = View.VISIBLE + } + } + } + } + + private fun setRethinkIpLogsAdapter() { + val layoutManager = LinearLayoutManager(this) + b.aadMostContactedIpsRv.layoutManager = layoutManager + val adapter = AppWiseIpsAdapter(this, this, uid, isRethinkApp) + networkLogsViewModel.getRethinkIpLogsLimited().observe(this) { + adapter.submitData(this.lifecycle, it) + } + b.aadMostContactedIpsRv.adapter = adapter + + adapter.addLoadStateListener { + if (it.append.endOfPaginationReached) { + if (adapter.itemCount < 1) { + b.aadMostContactedIpsRl.visibility = View.GONE + } else { + b.aadMostContactedIpsRl.visibility = View.VISIBLE + } + } + } + } + private fun setIpAdapter() { b.aadMostContactedIpsRv.setHasFixedSize(true) val layoutManager = LinearLayoutManager(this) b.aadMostContactedIpsRv.layoutManager = layoutManager - val adapter = AppWiseIpsAdapter(this, this, uid) + val adapter = AppWiseIpsAdapter(this, this, uid, isRethinkApp) networkLogsViewModel.getIpLogsLimited(uid).observe(this) { adapter.submitData(this.lifecycle, it) } diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseDomainLogsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseDomainLogsActivity.kt index c63382b97..8c94e5630 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseDomainLogsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseDomainLogsActivity.kt @@ -20,6 +20,7 @@ import android.content.res.ColorStateList import android.content.res.Configuration import android.graphics.drawable.Drawable import android.os.Bundle +import android.view.View import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.SearchView @@ -31,7 +32,6 @@ import com.bumptech.glide.Glide import com.celzero.bravedns.R import com.celzero.bravedns.adapter.AppWiseDomainsAdapter import com.celzero.bravedns.database.AppInfo -import com.celzero.bravedns.database.ConnectionTrackerRepository import com.celzero.bravedns.databinding.ActivityAppWiseDomainLogsBinding import com.celzero.bravedns.service.FirewallManager import com.celzero.bravedns.service.PersistentState @@ -57,10 +57,10 @@ class AppWiseDomainLogsActivity : private val persistentState by inject() private val networkLogsViewModel: AppConnectionsViewModel by viewModel() - private val connectionTrackerRepository by inject() private var uid: Int = INVALID_UID private var layoutManager: RecyclerView.LayoutManager? = null private lateinit var appInfo: AppInfo + private var isRethink = false private fun Context.isDarkThemeOn(): Boolean { return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == @@ -74,10 +74,18 @@ class AppWiseDomainLogsActivity : if (uid == INVALID_UID) { finish() } - init() - setAdapter() - observeNetworkLogSize() - setClickListener() + if (Utilities.getApplicationInfo(this, this.packageName)?.uid == uid) { + isRethink = true + init() + setRethinkAdapter() + observeRethinkNetworkLogSize() + b.toggleGroup.addOnButtonCheckedListener(listViewToggleListener) + } else { + init() + setAdapter() + observeNetworkLogSize() + setClickListener() + } } private fun setTabbedViewTxt() { @@ -147,6 +155,7 @@ class AppWiseDomainLogsActivity : val appNameTruncated = appName.substring(0, appName.length.coerceAtMost(10)) val hint = getString(R.string.two_argument_colon, appNameTruncated, getString(R.string.search_custom_domains)) b.awlSearch.queryHint = hint + b.awlSearch.findViewById(androidx.appcompat.R.id.search_src_text).textSize = 14f return } @@ -185,44 +194,93 @@ class AppWiseDomainLogsActivity : b.awlRecyclerConnection.setHasFixedSize(true) layoutManager = LinearLayoutManager(this) b.awlRecyclerConnection.layoutManager = layoutManager - val recyclerAdapter = AppWiseDomainsAdapter(this, this, uid) + val recyclerAdapter = AppWiseDomainsAdapter(this, this, uid, isRethink) networkLogsViewModel.appDomainLogs.observe(this) { recyclerAdapter.submitData(this.lifecycle, it) } b.awlRecyclerConnection.adapter = recyclerAdapter + + recyclerAdapter.addLoadStateListener { + if (it.append.endOfPaginationReached) { + if (recyclerAdapter.itemCount < 1) { + showNoRulesUi() + hideRulesUi() + } else { + hideNoRulesUi() + showRulesUi() + } + } + } + } + + private fun setRethinkAdapter() { + networkLogsViewModel.setUid(uid) + b.awlRecyclerConnection.setHasFixedSize(true) + layoutManager = LinearLayoutManager(this) + b.awlRecyclerConnection.layoutManager = layoutManager + val recyclerAdapter = AppWiseDomainsAdapter(this, this, uid, isRethink) + networkLogsViewModel.rinrDomainLogs.observe(this) { + recyclerAdapter.submitData(this.lifecycle, it) + } + b.awlRecyclerConnection.adapter = recyclerAdapter + + recyclerAdapter.addLoadStateListener { + if (it.append.endOfPaginationReached) { + if (recyclerAdapter.itemCount < 1) { + showNoRulesUi() + hideRulesUi() + } else { + hideNoRulesUi() + showRulesUi() + } + } + } } private fun observeNetworkLogSize() { - networkLogsViewModel.getConnectionsCount(uid).observe(this) { + networkLogsViewModel.getDomainCount(uid).observe(this) { if (it == null) return@observe if (it <= 0) { showNoRulesUi() hideRulesUi() - return@observe + } else { + hideNoRulesUi() + showRulesUi() } + } + } - hideNoRulesUi() - showRulesUi() + private fun observeRethinkNetworkLogSize() { + networkLogsViewModel.getRinRDomainCount().observe(this) { + if (it == null) return@observe + + if (it <= 0) { + showNoRulesUi() + hideRulesUi() + } else { + hideNoRulesUi() + showRulesUi() + } } } private fun showNoRulesUi() { - b.awlNoRulesRl.visibility = android.view.View.VISIBLE + b.awlNoRulesRl.visibility = View.VISIBLE } private fun hideRulesUi() { - b.awlCardViewTop.visibility = android.view.View.GONE - b.awlRecyclerConnection.visibility = android.view.View.GONE + b.awlCardViewTop.visibility = View.GONE + b.awlRecyclerConnection.visibility = View.GONE } private fun hideNoRulesUi() { - b.awlNoRulesRl.visibility = android.view.View.GONE + b.awlNoRulesRl.visibility = View.GONE } private fun showRulesUi() { - b.awlCardViewTop.visibility = android.view.View.VISIBLE - b.awlRecyclerConnection.visibility = android.view.View.VISIBLE + b.awlCardViewTop.visibility = View.VISIBLE + b.awlRecyclerConnection.visibility = View.VISIBLE } override fun onQueryTextSubmit(query: String): Boolean { @@ -251,7 +309,7 @@ class AppWiseDomainLogsActivity : } private fun deleteAppLogs() { - io { connectionTrackerRepository.clearLogsByUid(uid) } + io { networkLogsViewModel.deleteLogs(uid) } } private fun io(f: suspend () -> Unit): Job { diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseIpLogsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseIpLogsActivity.kt index 3d3e0c9a5..a3d8613cc 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseIpLogsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseIpLogsActivity.kt @@ -20,6 +20,7 @@ import android.content.res.ColorStateList import android.content.res.Configuration import android.graphics.drawable.Drawable import android.os.Bundle +import android.view.View import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.SearchView @@ -31,7 +32,6 @@ import com.bumptech.glide.Glide import com.celzero.bravedns.R import com.celzero.bravedns.adapter.AppWiseIpsAdapter import com.celzero.bravedns.database.AppInfo -import com.celzero.bravedns.database.ConnectionTrackerRepository import com.celzero.bravedns.databinding.ActivityAppWiseIpLogsBinding import com.celzero.bravedns.service.FirewallManager import com.celzero.bravedns.service.PersistentState @@ -56,10 +56,10 @@ class AppWiseIpLogsActivity : private val persistentState by inject() private val networkLogsViewModel: AppConnectionsViewModel by viewModel() - private val connectionTrackerRepository by inject() private var uid: Int = INVALID_UID private var layoutManager: RecyclerView.LayoutManager? = null private lateinit var appInfo: AppInfo + private var isRethink = false private fun Context.isDarkThemeOn(): Boolean { return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == @@ -73,10 +73,18 @@ class AppWiseIpLogsActivity : if (uid == INVALID_UID) { finish() } - init() - setAdapter() - observeNetworkLogSize() - setClickListener() + if (Utilities.getApplicationInfo(this, this.packageName)?.uid == uid) { + isRethink = true + init() + setRethinkAdapter() + observeRethinkNetworkLogSize() + b.toggleGroup.addOnButtonCheckedListener(listViewToggleListener) + } else { + init() + setAdapter() + observeNetworkLogSize() + setClickListener() + } } private fun init() { @@ -94,10 +102,11 @@ class AppWiseIpLogsActivity : uiCtx { this.appInfo = appInfo - b.awlAppDetailName.text = appName(packages.count()) + val appName = appName(packages.count()) + updateAppNameInSearchHint(appName) displayIcon( Utilities.getIcon(this, appInfo.packageName, appInfo.appName), - b.awlAppDetailIcon + b.awlAppDetailIcon1 ) } } @@ -171,46 +180,106 @@ class AppWiseIpLogsActivity : b.awlRecyclerConnection.setHasFixedSize(true) layoutManager = LinearLayoutManager(this) b.awlRecyclerConnection.layoutManager = layoutManager - val recyclerAdapter = AppWiseIpsAdapter(this, this, uid) + val recyclerAdapter = AppWiseIpsAdapter(this, this, uid, isRethink) networkLogsViewModel.appIpLogs.observe(this) { recyclerAdapter.submitData(this.lifecycle, it) } b.awlRecyclerConnection.adapter = recyclerAdapter + + recyclerAdapter.addLoadStateListener { + if (it.append.endOfPaginationReached) { + if (recyclerAdapter.itemCount < 1) { + showNoRulesUi() + hideRulesUi() + } else { + hideNoRulesUi() + showRulesUi() + } + } + } + } + + private fun setRethinkAdapter() { + networkLogsViewModel.setUid(uid) + b.awlRecyclerConnection.setHasFixedSize(true) + layoutManager = LinearLayoutManager(this) + b.awlRecyclerConnection.layoutManager = layoutManager + val recyclerAdapter = AppWiseIpsAdapter(this, this, uid, isRethink) + networkLogsViewModel.rinrIpLogs.observe(this) { + recyclerAdapter.submitData(this.lifecycle, it) + } + b.awlRecyclerConnection.adapter = recyclerAdapter + + recyclerAdapter.addLoadStateListener { + if (it.append.endOfPaginationReached) { + if (recyclerAdapter.itemCount < 1) { + showNoRulesUi() + hideRulesUi() + } else { + hideNoRulesUi() + showRulesUi() + } + } + } } private fun observeNetworkLogSize() { - networkLogsViewModel.getConnectionsCount(uid).observe(this) { + networkLogsViewModel.getIpCount(uid).observe(this) { if (it == null) return@observe if (it <= 0) { showNoRulesUi() hideRulesUi() - return@observe + } else { + hideNoRulesUi() + showRulesUi() } + } + } - hideNoRulesUi() - showRulesUi() + private fun observeRethinkNetworkLogSize() { + networkLogsViewModel.getRinrIpCount().observe(this) { + if (it == null) return@observe + + if (it <= 0) { + showNoRulesUi() + hideRulesUi() + } else { + hideNoRulesUi() + showRulesUi() + } } } + private fun updateAppNameInSearchHint(appName: String) { + val appNameTruncated = appName.substring(0, appName.length.coerceAtMost(10)) + val hint = getString( + R.string.two_argument_colon, + appNameTruncated, + getString(R.string.search_universal_ips) + ) + b.awlSearch.queryHint = hint + b.awlSearch.findViewById(androidx.appcompat.R.id.search_src_text).textSize = + 14f + return + } + private fun showNoRulesUi() { - b.awlNoRulesRl.visibility = android.view.View.VISIBLE + b.awlNoRulesRl.visibility = View.VISIBLE } private fun hideRulesUi() { - b.awlCardViewTop.visibility = android.view.View.GONE - b.awlAppDetailRl.visibility = android.view.View.GONE - b.awlRecyclerConnection.visibility = android.view.View.GONE + b.awlCardViewTop.visibility = View.GONE + b.awlRecyclerConnection.visibility = View.GONE } private fun hideNoRulesUi() { - b.awlNoRulesRl.visibility = android.view.View.GONE + b.awlNoRulesRl.visibility = View.GONE } private fun showRulesUi() { - b.awlCardViewTop.visibility = android.view.View.VISIBLE - b.awlAppDetailRl.visibility = android.view.View.VISIBLE - b.awlRecyclerConnection.visibility = android.view.View.VISIBLE + b.awlCardViewTop.visibility = View.VISIBLE + b.awlRecyclerConnection.visibility = View.VISIBLE } override fun onQueryTextSubmit(query: String): Boolean { @@ -239,7 +308,9 @@ class AppWiseIpLogsActivity : } private fun deleteAppLogs() { - io { connectionTrackerRepository.clearLogsByUid(uid) } + io { + networkLogsViewModel.deleteLogs(uid) + } } private fun io(f: suspend () -> Unit): Job { diff --git a/app/src/full/java/com/celzero/bravedns/viewmodel/AppConnectionsViewModel.kt b/app/src/full/java/com/celzero/bravedns/viewmodel/AppConnectionsViewModel.kt index 72deb940f..9e78d8aba 100644 --- a/app/src/full/java/com/celzero/bravedns/viewmodel/AppConnectionsViewModel.kt +++ b/app/src/full/java/com/celzero/bravedns/viewmodel/AppConnectionsViewModel.kt @@ -27,9 +27,10 @@ import androidx.paging.cachedIn import androidx.paging.liveData import com.celzero.bravedns.data.AppConnection import com.celzero.bravedns.database.ConnectionTrackerDAO +import com.celzero.bravedns.database.RethinkLogDao import com.celzero.bravedns.util.Constants -class AppConnectionsViewModel(private val nwlogDao: ConnectionTrackerDAO) : ViewModel() { +class AppConnectionsViewModel(private val nwlogDao: ConnectionTrackerDAO, private val rinrDao: RethinkLogDao) : ViewModel() { private var ipFilter: MutableLiveData = MutableLiveData() private var domainFilter: MutableLiveData = MutableLiveData() private var uid: Int = Constants.INVALID_UID @@ -101,6 +102,31 @@ class AppConnectionsViewModel(private val nwlogDao: ConnectionTrackerDAO) : View val appIpLogs = ipFilter.switchMap { input -> fetchIpLogs(uid, input) } val appDomainLogs = domainFilter.switchMap { input -> fetchAppDomainLogs(uid, input) } + val rinrIpLogs = ipFilter.switchMap { input -> fetchRinrIpLogs(input) } + val rinrDomainLogs = domainFilter.switchMap { input -> fetchRinrDomainLogs(input) } + + private fun fetchRinrIpLogs(input: String): LiveData> { + val to = getStartTime() + return if (input.isEmpty()) { + Pager(pagingConfig) { rinrDao.getIpLogs(to) } + } else { + Pager(pagingConfig) { rinrDao.getIpLogsFiltered(to, "%$input%") } + } + .liveData + .cachedIn(viewModelScope) + } + + private fun fetchRinrDomainLogs(input: String): LiveData> { + val to = getStartTime() + return if (input.isEmpty()) { + Pager(pagingConfig) { rinrDao.getDomainLogs(to) } + } else { + Pager(pagingConfig) { rinrDao.getDomainLogsFiltered(to, "%$input%") } + } + .liveData + .cachedIn(viewModelScope) + } + private fun fetchIpLogs(uid: Int, input: String): LiveData> { val to = getStartTime() return if (input.isEmpty()) { @@ -123,18 +149,43 @@ class AppConnectionsViewModel(private val nwlogDao: ConnectionTrackerDAO) : View .cachedIn(viewModelScope) } + fun deleteLogs(uid: Int) { + // delete based on the time category + when (timeCategory) { + TimeCategory.ONE_HOUR -> { + nwlogDao.clearLogsByTime(uid, System.currentTimeMillis() - ONE_HOUR_MILLIS) + } + + TimeCategory.TWENTY_FOUR_HOUR -> { + nwlogDao.clearLogsByTime(uid, System.currentTimeMillis() - ONE_DAY_MILLIS) + } + + TimeCategory.SEVEN_DAYS -> { + nwlogDao.clearLogsByUid(uid) // similar to clearing logs for uid + } + } + } + private fun getStartTime(): Long { return startTime.value ?: (System.currentTimeMillis() - ONE_HOUR_MILLIS) } - fun getConnectionsCount(uid: Int): LiveData { + fun getIpCount(uid: Int): LiveData { return nwlogDao.getAppConnectionsCount(uid) } - fun getAppDomainConnectionsCount(uid: Int): LiveData { + fun getRinrIpCount(): LiveData { + return rinrDao.getAppConnectionsCount() + } + + fun getDomainCount(uid: Int): LiveData { return nwlogDao.getAppDomainConnectionsCount(uid) } + fun getRinRDomainCount(): LiveData { + return rinrDao.getAppDomainConnectionsCount() + } + fun getDomainLogsLimited(uid: Int): LiveData> { val to = System.currentTimeMillis() - ONE_WEEK_MILLIS return Pager(pagingConfig) { nwlogDao.getAppDomainLogsLimited(uid, to) } @@ -142,6 +193,21 @@ class AppConnectionsViewModel(private val nwlogDao: ConnectionTrackerDAO) : View .cachedIn(viewModelScope) } + fun getRethinkDomainLogsLimited(): LiveData> { + val to = System.currentTimeMillis() - ONE_WEEK_MILLIS + return Pager(pagingConfig) { rinrDao.getDomainLogsLimited(to) } + .liveData + .cachedIn(viewModelScope) + } + + fun getRethinkIpLogsLimited(): LiveData> { + val to = System.currentTimeMillis() - ONE_WEEK_MILLIS + return Pager(pagingConfig) { rinrDao.getIpLogsLimited(to) } + .liveData + .cachedIn(viewModelScope) + } + + fun getIpLogsLimited(uid: Int): LiveData> { val to = System.currentTimeMillis() - ONE_WEEK_MILLIS return Pager(pagingConfig) { nwlogDao.getAppIpLogsLimited(uid, to) } diff --git a/app/src/main/java/com/celzero/bravedns/database/ConnectionTracker.kt b/app/src/main/java/com/celzero/bravedns/database/ConnectionTracker.kt index b4b9be525..6820024af 100644 --- a/app/src/main/java/com/celzero/bravedns/database/ConnectionTracker.kt +++ b/app/src/main/java/com/celzero/bravedns/database/ConnectionTracker.kt @@ -29,6 +29,7 @@ import com.celzero.bravedns.util.Constants.Companion.INIT_TIME_MS Index(value = arrayOf("dnsQuery"), unique = false), Index(value = arrayOf("blockedByRule"), unique = false), Index(value = arrayOf("isBlocked", "timeStamp"), unique = false), + Index(value = arrayOf("connId"), unique = false) ] ) class ConnectionTracker { diff --git a/app/src/main/java/com/celzero/bravedns/database/RethinkLog.kt b/app/src/main/java/com/celzero/bravedns/database/RethinkLog.kt index 2b42ca626..961a70d6e 100644 --- a/app/src/main/java/com/celzero/bravedns/database/RethinkLog.kt +++ b/app/src/main/java/com/celzero/bravedns/database/RethinkLog.kt @@ -27,6 +27,7 @@ import com.celzero.bravedns.util.Constants.Companion.INIT_TIME_MS Index(value = arrayOf("ipAddress"), unique = false), Index(value = arrayOf("appName"), unique = false), Index(value = arrayOf("dnsQuery"), unique = false), + Index(value = arrayOf("connId"), unique = false) ] ) class RethinkLog { diff --git a/app/src/main/java/com/celzero/bravedns/database/RethinkLogDao.kt b/app/src/main/java/com/celzero/bravedns/database/RethinkLogDao.kt index b4fdce286..51a664303 100644 --- a/app/src/main/java/com/celzero/bravedns/database/RethinkLogDao.kt +++ b/app/src/main/java/com/celzero/bravedns/database/RethinkLogDao.kt @@ -35,7 +35,7 @@ interface RethinkLogDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(log: RethinkLog) @Insert(onConflict = OnConflictStrategy.REPLACE) - fun insertBatch(connTrackerList: List) + fun insertBatch(logs: List) @Query( "update RethinkLog set downloadBytes = :downloadBytes, uploadBytes = :uploadBytes, duration = :duration, synack = :synack, message = :message where connId = :connId" @@ -82,8 +82,13 @@ interface RethinkLogDao { ) fun getLogsForAppFiltered(uid: Int, ipAddress: String): PagingSource - @Query("select count(DISTINCT(ipAddress)) from RethinkLog where uid = :uid") - fun getAppConnectionsCount(uid: Int): LiveData + @Query("select count(DISTINCT(ipAddress)) from RethinkLog") + fun getAppConnectionsCount(): LiveData + + @Query( + "select count(DISTINCT(dnsQuery)) from ConnectionTracker where dnsQuery != ''" + ) + fun getAppDomainConnectionsCount(): LiveData @Query("select * from RethinkLog where isBlocked = 1 order by id desc LIMIT $MAX_LOGS") fun getBlockedConnectionsFiltered(): PagingSource @@ -113,7 +118,39 @@ interface RethinkLogDao { fun getLeastLoggedTime(): Long @Query( - "SELECT uid, SUM(uploadBytes) AS uploadBytes, SUM(downloadBytes) AS downloadBytes FROM RethinkLog where timeStamp >= :fromTime and timeStamp <= :toTime GROUP BY uid" + "SELECT uid, SUM(uploadBytes) AS uploadBytes, SUM(downloadBytes) AS downloadBytes FROM RethinkLog where timeStamp >= :fromTime and timeStamp <= :toTime" + ) + fun getDataUsage(fromTime: Long, toTime: Long): DataUsage + + @Query( + "SELECT uid, '' as ipAddress, port, COUNT(dnsQuery) as count, flag as flag, 0 as blocked, dnsQuery as appOrDnsName FROM RethinkLog WHERE timeStamp > :to and dnsQuery != '' GROUP BY dnsQuery ORDER BY count DESC LIMIT 3" + ) + fun getDomainLogsLimited(to: Long): PagingSource + + @Query( + "SELECT uid, ipAddress, port, COUNT(ipAddress) as count, flag as flag, 0 as blocked, '' as appOrDnsName FROM RethinkLog WHERE timeStamp > :to GROUP BY uid, ipAddress, port ORDER BY count DESC LIMIT 3" + ) + fun getIpLogsLimited(to: Long): PagingSource + + @Query( + "SELECT uid, ipAddress, port, COUNT(ipAddress) as count, flag as flag, 0 as blocked, GROUP_CONCAT(DISTINCT dnsQuery) as appOrDnsName FROM RethinkLog WHERE timeStamp > :to GROUP BY uid, ipAddress, port ORDER BY count DESC" ) - fun getDataUsage(fromTime: Long, toTime: Long): List + fun getIpLogs(to: Long): PagingSource + + @Query( + "SELECT uid, ipAddress, port, COUNT(ipAddress) as count, flag as flag, 0 as blocked, GROUP_CONCAT(DISTINCT dnsQuery) as appOrDnsName FROM RethinkLog WHERE timeStamp > :to and ipAddress like :query GROUP BY uid, ipAddress, port ORDER BY count DESC" + ) + fun getIpLogsFiltered(to: Long, query: String): PagingSource + + @Query( + "SELECT uid, GROUP_CONCAT(DISTINCT ipAddress) as ipAddress, port, COUNT(dnsQuery) as count, flag as flag, 0 as blocked, dnsQuery as appOrDnsName FROM RethinkLog WHERE timeStamp > :to and dnsQuery != '' GROUP BY dnsQuery ORDER BY count DESC" + ) + fun getDomainLogs(to: Long): PagingSource + + + @Query( + "SELECT uid, GROUP_CONCAT(DISTINCT ipAddress) as ipAddress, port, COUNT(dnsQuery) as count, flag as flag, 0 as blocked, dnsQuery as appOrDnsName FROM RethinkLog WHERE timeStamp > :to and dnsQuery != '' and dnsQuery like :query GROUP BY dnsQuery ORDER BY count DESC" + ) + fun getDomainLogsFiltered(to: Long, query: String): PagingSource + } diff --git a/app/src/main/java/com/celzero/bravedns/database/RethinkLogRepository.kt b/app/src/main/java/com/celzero/bravedns/database/RethinkLogRepository.kt index 27cb38f02..9b1fba09f 100644 --- a/app/src/main/java/com/celzero/bravedns/database/RethinkLogRepository.kt +++ b/app/src/main/java/com/celzero/bravedns/database/RethinkLogRepository.kt @@ -17,6 +17,7 @@ package com.celzero.bravedns.database import androidx.lifecycle.LiveData import com.celzero.bravedns.data.ConnectionSummary +import com.celzero.bravedns.data.DataUsage class RethinkLogRepository(private val logDao: RethinkLogDao) { @@ -52,4 +53,8 @@ class RethinkLogRepository(private val logDao: RethinkLogDao) { fun logsCount(): LiveData { return logDao.logsCount() } + + fun getDataUsage(from: Long, to: Long): DataUsage { + return logDao.getDataUsage(from, to) + } } From 4083e754a30171994dcdf372d1fa12b507860d57 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sun, 30 Jun 2024 01:48:49 +0530 Subject: [PATCH 023/188] bugrpt: rmv unwanted close call for zipentry --- .../full/java/com/celzero/bravedns/scheduler/BugReportZipper.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/full/java/com/celzero/bravedns/scheduler/BugReportZipper.kt b/app/src/full/java/com/celzero/bravedns/scheduler/BugReportZipper.kt index 02cfafd1a..4cd54c35d 100644 --- a/app/src/full/java/com/celzero/bravedns/scheduler/BugReportZipper.kt +++ b/app/src/full/java/com/celzero/bravedns/scheduler/BugReportZipper.kt @@ -234,7 +234,6 @@ object BugReportZipper { while (input.read(buffer).also { bytesRead = it } != -1) { output.write(buffer, 0, bytesRead) } - output.close() } catch (e: Exception) { Logger.w(LOG_TAG_BUG_REPORT, "Exception while copying the file", e) } From 8c68c8a6667a235b67f7cb2624d0cd861da0a05d Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sun, 30 Jun 2024 01:49:22 +0530 Subject: [PATCH 024/188] logger: purge console logs based on time instead of limit --- .../bravedns/scheduler/PurgeConsoleLogs.kt | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/scheduler/PurgeConsoleLogs.kt b/app/src/full/java/com/celzero/bravedns/scheduler/PurgeConsoleLogs.kt index 9570bc391..22e127834 100644 --- a/app/src/full/java/com/celzero/bravedns/scheduler/PurgeConsoleLogs.kt +++ b/app/src/full/java/com/celzero/bravedns/scheduler/PurgeConsoleLogs.kt @@ -6,20 +6,23 @@ import androidx.work.WorkerParameters import com.celzero.bravedns.database.ConsoleLogRepository import org.koin.core.component.KoinComponent import org.koin.core.component.inject +import java.util.concurrent.TimeUnit class PurgeConsoleLogs(val context: Context, workerParameters: WorkerParameters) : CoroutineWorker(context, workerParameters), KoinComponent { - companion object { - const val LOG_LIMIT = 20000 - } - private val consoleLogRepository by inject() + companion object { + const val MAX_TIME: Long = 4 + } override suspend fun doWork(): Result { - // delete old logs will limit the logs count to 20000 - consoleLogRepository.deleteOldLogs(LOG_LIMIT) - Logger.v(Logger.LOG_TAG_APP_DB, "Purging console logs") + // delete logs which are older than MAX_TIME hrs + val threshold = TimeUnit.HOURS.toMillis(MAX_TIME) + val currTime = System.currentTimeMillis() + val time = currTime - threshold + + consoleLogRepository.deleteOldLogs(time) return Result.success() } From a40efa90291107759063ff497e9fb715436a169e Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 1 Jul 2024 20:22:35 +0530 Subject: [PATCH 025/188] firestack: Stats rename to RouterStats --- .../com/celzero/bravedns/adapter/OneWgConfigAdapter.kt | 10 +++++----- .../com/celzero/bravedns/adapter/WgConfigAdapter.kt | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt index f4876fb34..7872dead9 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt @@ -31,7 +31,7 @@ import androidx.paging.PagingDataAdapter import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import backend.Backend -import backend.Stats +import backend.RouterStats import com.celzero.bravedns.R import com.celzero.bravedns.database.WgConfigFiles import com.celzero.bravedns.databinding.ListItemWgOneInterfaceBinding @@ -212,7 +212,7 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns } } - private fun updateStatusUi(config: WgConfigFiles, statusId: Long?, stats: Stats?) { + private fun updateStatusUi(config: WgConfigFiles, statusId: Long?, stats: RouterStats?) { if (config.isActive && VpnController.hasTunnel()) { b.interfaceDetailCard.strokeWidth = 2 b.oneWgCheck.isChecked = true @@ -305,7 +305,7 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns context.getString(R.string.lbl_disabled).replaceFirstChar(Char::titlecase) } - private fun getUpTime(stats: Stats?): CharSequence { + private fun getUpTime(stats: RouterStats?): CharSequence { if (stats == null) { return "" } @@ -319,7 +319,7 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns ) } - private fun getRxTx(stats: Stats?): String { + private fun getRxTx(stats: RouterStats?): String { if (stats == null) return "" val rx = context.getString( @@ -334,7 +334,7 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns return context.getString(R.string.two_argument_space, tx, rx) } - private fun getHandshakeTime(stats: Stats?): CharSequence { + private fun getHandshakeTime(stats: RouterStats?): CharSequence { if (stats == null) { return "" } diff --git a/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt index 8f9293660..ef1d8d4bc 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt @@ -30,7 +30,7 @@ import androidx.paging.PagingDataAdapter import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import backend.Backend -import backend.Stats +import backend.RouterStats import com.celzero.bravedns.R import com.celzero.bravedns.database.WgConfigFiles import com.celzero.bravedns.database.WgConfigFilesImmutable @@ -277,7 +277,7 @@ class WgConfigAdapter(private val context: Context) : } } - private fun updateStatusUi(config: WgConfigFiles, statusId: Long?, stats: Stats?) { + private fun updateStatusUi(config: WgConfigFiles, statusId: Long?, stats: RouterStats?) { if (config.isActive) { b.interfaceSwitch.isChecked = true b.interfaceDetailCard.strokeWidth = 2 @@ -369,7 +369,7 @@ class WgConfigAdapter(private val context: Context) : } } - private fun getRxTx(stats: Stats?): String { + private fun getRxTx(stats: RouterStats?): String { if (stats == null) return "" val rx = context.getString( @@ -384,7 +384,7 @@ class WgConfigAdapter(private val context: Context) : return context.getString(R.string.two_argument_space, tx, rx) } - private fun getUpTime(stats: Stats?): CharSequence { + private fun getUpTime(stats: RouterStats?): CharSequence { if (stats == null) { return "" } @@ -398,7 +398,7 @@ class WgConfigAdapter(private val context: Context) : ) } - private fun getHandshakeTime(stats: Stats?): CharSequence { + private fun getHandshakeTime(stats: RouterStats?): CharSequence { if (stats == null) { return "" } From 03ca6548b74263046775f5e00b925579dfc95624 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 1 Jul 2024 20:23:03 +0530 Subject: [PATCH 026/188] logger: replace logger stmts to log in bug rpt worker --- .../bravedns/scheduler/EnhancedBugReport.kt | 62 +++++++++---------- 1 file changed, 31 insertions(+), 31 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/scheduler/EnhancedBugReport.kt b/app/src/full/java/com/celzero/bravedns/scheduler/EnhancedBugReport.kt index ebfb3fd1e..5635a0cb5 100644 --- a/app/src/full/java/com/celzero/bravedns/scheduler/EnhancedBugReport.kt +++ b/app/src/full/java/com/celzero/bravedns/scheduler/EnhancedBugReport.kt @@ -15,10 +15,10 @@ */ package com.celzero.bravedns.scheduler -import Logger import Logger.LOG_TAG_BUG_REPORT import android.content.Context import android.os.Build +import android.util.Log import androidx.annotation.RequiresApi import com.celzero.bravedns.util.Constants import com.celzero.bravedns.util.Utilities @@ -47,11 +47,11 @@ object EnhancedBugReport { // add the logs to the zip file // close the zip file val zipFilePath = File(context.filesDir.canonicalPath + File.separator + ZIP_FILE_NAME) - Logger.d(LOG_TAG_BUG_REPORT, "zip file path: $zipFilePath") + Log.d(LOG_TAG_BUG_REPORT, "zip file path: $zipFilePath") val zipOutputStream = ZipOutputStream(FileOutputStream(zipFilePath)) val folder = getFolderPath(context.filesDir) ?: return val files = File(folder).listFiles() - Logger.d(LOG_TAG_BUG_REPORT, "files to add to zip: ${files?.size}") + Log.d(LOG_TAG_BUG_REPORT, "files to add to zip: ${files?.size}") files?.forEach { file -> val inputStream = FileInputStream(file) val zipEntry = ZipEntry(file.name) @@ -61,33 +61,33 @@ object EnhancedBugReport { } zipOutputStream.close() } catch (e: FileNotFoundException) { - Logger.e(LOG_TAG_BUG_REPORT, "err adding logs to zip file: ${e.message}", e) + Log.e(LOG_TAG_BUG_REPORT, "err adding logs to zip file: ${e.message}", e) } catch (e: ZipException) { - Logger.e(LOG_TAG_BUG_REPORT, "err adding logs to zip file: ${e.message}", e) + Log.e(LOG_TAG_BUG_REPORT, "err adding logs to zip file: ${e.message}", e) } catch (e: Exception) { - Logger.e(LOG_TAG_BUG_REPORT, "err adding logs to zip file: ${e.message}", e) + Log.e(LOG_TAG_BUG_REPORT, "err adding logs to zip file: ${e.message}", e) } - Logger.i(LOG_TAG_BUG_REPORT, "logs added to zip file") + Log.i(LOG_TAG_BUG_REPORT, "logs added to zip file") } fun writeLogsToFile(context: Context, logs: String) { try { val file = getFileToWrite(context) if (file == null) { - Logger.e(LOG_TAG_BUG_REPORT, "file name is null, cannot write logs to file") + Log.e(LOG_TAG_BUG_REPORT, "file name is null, cannot write logs to file") return } val time = Utilities.convertLongToTime(System.currentTimeMillis(), Constants.TIME_FORMAT_3) val l = "$time: $logs\n" // append a new line character file.appendText(l, Charset.defaultCharset()) } catch (e: Exception) { - Logger.e(LOG_TAG_BUG_REPORT, "err writing logs to file: ${e.message}", e) + Log.e(LOG_TAG_BUG_REPORT, "err writing logs to file: ${e.message}", e) } } private fun getFileToWrite(context: Context): File? { val file = getTombstoneFile(context) - Logger.d(LOG_TAG_BUG_REPORT, "file to write logs: ${file?.name}") + Log.d(LOG_TAG_BUG_REPORT, "file to write logs: ${file?.name}") return file } @@ -95,20 +95,20 @@ object EnhancedBugReport { try { val folderPath = getFolderPath(context.filesDir) if (folderPath == null) { - Logger.e(LOG_TAG_BUG_REPORT, "folder path is null, cannot get tombstone file") + Log.e(LOG_TAG_BUG_REPORT, "folder path is null, cannot get tombstone file") return null } val folder = File(folderPath) val files = folder.listFiles() if (files.isNullOrEmpty()) { - Logger.d(LOG_TAG_BUG_REPORT, "no files found in the tombstone folder") + Log.d(LOG_TAG_BUG_REPORT, "no files found in the tombstone folder") return createTombstoneFile(folderPath) } else { - Logger.d(LOG_TAG_BUG_REPORT, "files found in the tombstone folder") + Log.d(LOG_TAG_BUG_REPORT, "files found in the tombstone folder") return getLatestFile(files) ?: createTombstoneFile(folderPath) } } catch (e: Exception) { - Logger.e(LOG_TAG_BUG_REPORT, "err getting tombstone file: ${e.message}", e) + Log.e(LOG_TAG_BUG_REPORT, "err getting tombstone file: ${e.message}", e) return null } } @@ -120,13 +120,13 @@ object EnhancedBugReport { files.sortByDescending { it.name } var latestFile = files[0] var latestTimestamp = latestFile.lastModified() - Logger.vv(LOG_TAG_BUG_REPORT, "latest timestamp: $latestTimestamp, ${latestFile.name}") + Log.v(LOG_TAG_BUG_REPORT, "latest timestamp: $latestTimestamp, ${latestFile.name}") for (file in files) { - Logger.vv(LOG_TAG_BUG_REPORT, "file timestamp: ${file.lastModified()}, ${file.name}") + Log.v(LOG_TAG_BUG_REPORT, "file timestamp: ${file.lastModified()}, ${file.name}") if (file.lastModified() > latestTimestamp) { latestFile = file latestTimestamp = file.lastModified() - Logger.vv(LOG_TAG_BUG_REPORT, "updated timestamp: $latestTimestamp, ${latestFile.name}") + Log.v(LOG_TAG_BUG_REPORT, "updated timestamp: $latestTimestamp, ${latestFile.name}") } } @@ -140,13 +140,13 @@ object EnhancedBugReport { files.sortBy { it.name } var oldestFile = files[0] var oldestTimestamp = oldestFile.lastModified() - Logger.vv(LOG_TAG_BUG_REPORT, "oldest timestamp: $oldestTimestamp, ${oldestFile.name}") + Log.v(LOG_TAG_BUG_REPORT, "oldest timestamp: $oldestTimestamp, ${oldestFile.name}") for (file in files) { - Logger.vv(LOG_TAG_BUG_REPORT, "file timestamp: ${file.lastModified()}, ${file.name}") + Log.v(LOG_TAG_BUG_REPORT, "file timestamp: ${file.lastModified()}, ${file.name}") if (file.lastModified() < oldestTimestamp) { oldestFile = file oldestTimestamp = file.lastModified() - Logger.vv(LOG_TAG_BUG_REPORT, "updated timestamp: $oldestTimestamp, ${oldestFile.name}") + Log.v(LOG_TAG_BUG_REPORT, "updated timestamp: $oldestTimestamp, ${oldestFile.name}") } } @@ -157,12 +157,12 @@ object EnhancedBugReport { try { val zipFile = File(context.filesDir.canonicalPath + File.separator + ZIP_FILE_NAME) if (!zipFile.exists()) { - Logger.w(LOG_TAG_BUG_REPORT, "zip file is null, cannot add logs to zip file") + Log.w(LOG_TAG_BUG_REPORT, "zip file is null, cannot add logs to zip file") return null } return zipFile } catch (e: Exception) { - Logger.e(LOG_TAG_BUG_REPORT, "err getting tombstone zip file: ${e.message}", e) + Log.e(LOG_TAG_BUG_REPORT, "err getting tombstone zip file: ${e.message}", e) } return null } @@ -170,12 +170,12 @@ object EnhancedBugReport { private fun getLatestFile(files: Array): File? { try { if (files.isEmpty()) { - Logger.w(LOG_TAG_BUG_REPORT, "no files found in the tombstone folder") + Log.w(LOG_TAG_BUG_REPORT, "no files found in the tombstone folder") return null } val latestFile = findLatestFile(files) ?: return null if (latestFile.length() > MAX_FILE_SIZE) { - Logger.d(LOG_TAG_BUG_REPORT, "file size is more than 1MB, ${latestFile.name}") + Log.d(LOG_TAG_BUG_REPORT, "file size is more than 1MB, ${latestFile.name}") // create a new file val parent = latestFile.parent ?: return null return createTombstoneFile(parent) @@ -183,12 +183,12 @@ object EnhancedBugReport { // if the file count is more than MAX_TOMBSTONE_FILES, delete the oldest file if (files.size > MAX_TOMBSTONE_FILES) { val fileToDelete = findOldestFile(files) ?: return null - Logger.i(LOG_TAG_BUG_REPORT, "deleted the oldest file ${fileToDelete.name}, file count: ${files.size}") + Log.i(LOG_TAG_BUG_REPORT, "deleted the oldest file ${fileToDelete.name}, file count: ${files.size}") fileToDelete.delete() } return latestFile } catch (e: Exception) { - Logger.e(LOG_TAG_BUG_REPORT, "err getting latest file: ${e.message}", e) + Log.e(LOG_TAG_BUG_REPORT, "err getting latest file: ${e.message}", e) } return null } @@ -200,10 +200,10 @@ object EnhancedBugReport { val file = File(folderPath + File.separator + TOMBSTONE_FILE_NAME + ts + FILE_EXTENSION) file.createNewFile() - Logger.d(LOG_TAG_BUG_REPORT, "created tombstone file: ${file.name}") + Log.d(LOG_TAG_BUG_REPORT, "created tombstone file: ${file.name}") return file } catch (e: Exception) { - Logger.e(LOG_TAG_BUG_REPORT, "err creating tombstone file: ${e.message}", e) + Log.e(LOG_TAG_BUG_REPORT, "err creating tombstone file: ${e.message}", e) } return null } @@ -215,14 +215,14 @@ object EnhancedBugReport { val path = file.canonicalPath + File.separator + TOMBSTONE_DIR_NAME val folder = File(path) if (folder.exists()) { - Logger.vv(LOG_TAG_BUG_REPORT, "folder exists: $path") + Log.v(LOG_TAG_BUG_REPORT, "folder exists: $path") } else { folder.mkdir() - Logger.vv(LOG_TAG_BUG_REPORT, "folder created: $path") + Log.v(LOG_TAG_BUG_REPORT, "folder created: $path") } return path } catch (e: Exception) { - Logger.e(LOG_TAG_BUG_REPORT, "err getting folder path: ${e.message}", e) + Log.e(LOG_TAG_BUG_REPORT, "err getting folder path: ${e.message}", e) } return null } From 1ac297d0c1591326d2ae80712745c195ae67ed31 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 8 Jul 2024 20:04:41 +0530 Subject: [PATCH 027/188] logger: stop console logging after prolonged runtime --- .../com/celzero/bravedns/scheduler/PurgeConsoleLogs.kt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/src/full/java/com/celzero/bravedns/scheduler/PurgeConsoleLogs.kt b/app/src/full/java/com/celzero/bravedns/scheduler/PurgeConsoleLogs.kt index 22e127834..8a324e6cc 100644 --- a/app/src/full/java/com/celzero/bravedns/scheduler/PurgeConsoleLogs.kt +++ b/app/src/full/java/com/celzero/bravedns/scheduler/PurgeConsoleLogs.kt @@ -4,6 +4,7 @@ import android.content.Context import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import com.celzero.bravedns.database.ConsoleLogRepository +import com.celzero.bravedns.service.PersistentState import org.koin.core.component.KoinComponent import org.koin.core.component.inject import java.util.concurrent.TimeUnit @@ -12,9 +13,11 @@ class PurgeConsoleLogs(val context: Context, workerParameters: WorkerParameters) CoroutineWorker(context, workerParameters), KoinComponent { private val consoleLogRepository by inject() + private val persistentState by inject() companion object { const val MAX_TIME: Long = 4 + const val MAX_LOGS: Int = 200000 } override suspend fun doWork(): Result { // delete logs which are older than MAX_TIME hrs @@ -23,6 +26,13 @@ class PurgeConsoleLogs(val context: Context, workerParameters: WorkerParameters) val time = currTime - threshold consoleLogRepository.deleteOldLogs(time) + if (persistentState.consoleLogEnabled) { + // check if the logs are too many, if so stop the logging + val count = consoleLogRepository.getLogCount() + if (count > MAX_LOGS) { + persistentState.consoleLogEnabled = false + } + } return Result.success() } From f4f8578e5a3658d2caa49d3d559ed3df8b882031 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 8 Jul 2024 20:05:41 +0530 Subject: [PATCH 028/188] logger: format logging in debug mode with id --- .../java/com/celzero/bravedns/adapter/ConsoleLogAdapter.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/ConsoleLogAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/ConsoleLogAdapter.kt index 07bb3b2b9..c47d42b69 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/ConsoleLogAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/ConsoleLogAdapter.kt @@ -52,7 +52,7 @@ class ConsoleLogAdapter(private val context: Context) : } override fun onBindViewHolder(holder: ConsoleLogViewHolder, position: Int) { - val logInfo: ConsoleLog = getItem(position) ?: return + val logInfo = getItem(position) ?: return holder.update(logInfo) } @@ -61,7 +61,7 @@ class ConsoleLogAdapter(private val context: Context) : fun update(log: ConsoleLog) { // update the textview color with the first letter of the log level - val logLevel = log.message.first() + val logLevel = log.message.firstOrNull() ?: 'V' when (logLevel) { 'V' -> b.logDetail.setTextColor( @@ -91,7 +91,7 @@ class ConsoleLogAdapter(private val context: Context) : b.logDetail.text = log.message if (DEBUG) { b.logTimestamp.text = - " ${log.id} ${Utilities.convertLongToTime(log.timestamp, TIME_FORMAT_1)}" + "${log.id}\n${Utilities.convertLongToTime(log.timestamp, TIME_FORMAT_1)}" } else { b.logTimestamp.text = Utilities.convertLongToTime(log.timestamp, TIME_FORMAT_1) } From 068378fb81eed14e5383a93ee1f0a4da15d98251 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 8 Jul 2024 20:08:35 +0530 Subject: [PATCH 029/188] conn checks: reorganize custom IPs dialog components --- .../ui/activity/TunnelSettingsActivity.kt | 39 +++++++++++----- .../main/res/drawable/edittext_default.xml | 10 ++++- app/src/main/res/drawable/edittext_error.xml | 10 ++++- app/src/main/res/layout/dialog_input_ips.xml | 45 ++++++++++++------- 4 files changed, 72 insertions(+), 32 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/TunnelSettingsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/TunnelSettingsActivity.kt index 6ee4c9e85..0b82e6e49 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/TunnelSettingsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/TunnelSettingsActivity.kt @@ -46,6 +46,7 @@ import com.celzero.bravedns.util.InternetProtocol import com.celzero.bravedns.util.Themes import com.celzero.bravedns.util.UIUtils import com.celzero.bravedns.util.Utilities +import com.google.android.material.chip.Chip import com.google.android.material.dialog.MaterialAlertDialogBuilder import inet.ipaddr.IPAddress.IPVersion import inet.ipaddr.IPAddressString @@ -90,7 +91,7 @@ class TunnelSettingsActivity : AppCompatActivity(R.layout.activity_tunnel_settin // connectivity check b.settingsActivityConnectivityChecksSwitch.isChecked = persistentState.connectivityChecks // show ping ips - b.settingsActivityPingIpsRl.visibility = if (persistentState.connectivityChecks) View.VISIBLE else View.GONE + b.settingsActivityPingIpsBtn.visibility = if (persistentState.connectivityChecks) View.VISIBLE else View.GONE // exclude apps in proxy b.settingsActivityExcludeProxyAppsSwitch.isChecked = !persistentState.excludeAppsInProxy // for protocol translation, enable only on DNS/DNS+Firewall mode @@ -235,13 +236,13 @@ class TunnelSettingsActivity : AppCompatActivity(R.layout.activity_tunnel_settin b.settingsActivityConnectivityChecksSwitch.setOnCheckedChangeListener { _, isChecked -> persistentState.connectivityChecks = isChecked if (isChecked) { - b.settingsActivityPingIpsRl.visibility = View.VISIBLE + b.settingsActivityPingIpsBtn.visibility = View.VISIBLE } else { - b.settingsActivityPingIpsRl.visibility = View.GONE + b.settingsActivityPingIpsBtn.visibility = View.GONE } } - b.settingsActivityPingIpsRl.setOnClickListener { + b.settingsActivityPingIpsBtn.setOnClickListener { showPingIpsDialog() } } @@ -304,8 +305,9 @@ class TunnelSettingsActivity : AppCompatActivity(R.layout.activity_tunnel_settin val errorDrawable = ContextCompat.getDrawable(this, R.drawable.edittext_error) val saveBtn: AppCompatButton = dialogView.findViewById(R.id.save_button) - val testBtn: AppCompatButton = dialogView.findViewById(R.id.test_button) + val testBtn: AppCompatImageView = dialogView.findViewById(R.id.test_button) val cancelBtn: AppCompatButton = dialogView.findViewById(R.id.cancel_button) + val resetChip: Chip = dialogView.findViewById(R.id.reset_chip) val errorMsg: AppCompatTextView = dialogView.findViewById(R.id.error_message) @@ -334,6 +336,16 @@ class TunnelSettingsActivity : AppCompatActivity(R.layout.activity_tunnel_settin val dialog = alertBuilder.create() + resetChip.setOnClickListener { + // reset to default values + ip41.setText(Constants.ip4probes[0]) + ip42.setText(Constants.ip4probes[1]) + ip43.setText(Constants.ip4probes[2]) + ip61.setText(Constants.ip6probes[0]) + ip62.setText(Constants.ip6probes[1]) + ip63.setText(Constants.ip6probes[2]) + } + testBtn.setOnClickListener { try { progress41.visibility = View.VISIBLE @@ -406,7 +418,7 @@ class TunnelSettingsActivity : AppCompatActivity(R.layout.activity_tunnel_settin if (!valid41 || !valid42 || !valid43 || !valid61 || !valid62 || !valid63) { errorMsg.visibility = View.VISIBLE - errorMsg.text = "Invalid IP address. Please enter a valid IP address." + errorMsg.text = getString(R.string.cd_dns_proxy_error_text_1) return@setOnClickListener } else { errorMsg.visibility = View.VISIBLE @@ -485,13 +497,16 @@ class TunnelSettingsActivity : AppCompatActivity(R.layout.activity_tunnel_settin } } - private fun isValidIp(ipString: String, type: IPVersion): Boolean { - if (type.isIPv4) { - return IPAddressString(ipString).isValid() - } - if (type.isIPv6) { - return IPAddressString(ipString).isValid() + try { + if (type.isIPv4) { + return IPAddressString(ipString).toAddress().isIPv4 + } + if (type.isIPv6) { + return IPAddressString(ipString).toAddress().isIPv6 + } + } catch (e: Exception) { + Logger.i(LOG_TAG_UI, "err on ip validation: ${e.message}") } return false } diff --git a/app/src/main/res/drawable/edittext_default.xml b/app/src/main/res/drawable/edittext_default.xml index 7f40b0bab..5c86e9357 100644 --- a/app/src/main/res/drawable/edittext_default.xml +++ b/app/src/main/res/drawable/edittext_default.xml @@ -1,7 +1,13 @@ - + - + diff --git a/app/src/main/res/drawable/edittext_error.xml b/app/src/main/res/drawable/edittext_error.xml index e3badaf85..d3b858b65 100644 --- a/app/src/main/res/drawable/edittext_error.xml +++ b/app/src/main/res/drawable/edittext_error.xml @@ -1,7 +1,13 @@ - + - + diff --git a/app/src/main/res/layout/dialog_input_ips.xml b/app/src/main/res/layout/dialog_input_ips.xml index 3d0bba1cf..8c5bc930b 100644 --- a/app/src/main/res/layout/dialog_input_ips.xml +++ b/app/src/main/res/layout/dialog_input_ips.xml @@ -39,6 +39,28 @@ android:textColor="?attr/secondaryTextColor" android:textSize="18sp" /> + + + + + android:textColor="?attr/primaryTextColor" + android:text="@string/lbl_cancel" /> + android:text="@string/lbl_save" /> - - - From 6ca8c90a46d3bbb228a07f1f062934eb06da61c9 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 8 Jul 2024 20:10:50 +0530 Subject: [PATCH 030/188] wg-ui: resize textview for id --- .../bravedns/adapter/OneWgConfigAdapter.kt | 9 ++------- .../celzero/bravedns/adapter/WgConfigAdapter.kt | 8 ++------ .../layout/list_item_wg_general_interface.xml | 17 ++++++++++++----- .../res/layout/list_item_wg_one_interface.xml | 15 +++++++++++---- 4 files changed, 27 insertions(+), 22 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt index 7872dead9..954d54b52 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt @@ -116,13 +116,8 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns private var job: Job? = null fun update(config: WgConfigFiles) { - // limit the config name to 12 characters - b.interfaceNameText.text = - context.getString( - R.string.about_version_install_source, - config.name.take(11), - config.id.toString() - ) + b.interfaceNameText.text = config.name.take(12) + b.interfaceIdText.text = context.getString(R.string.single_argument_parenthesis, config.id.toString()) val isWgActive = config.isActive && VpnController.hasTunnel() b.oneWgCheck.isChecked = isWgActive setupClickListeners(config) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt index ef1d8d4bc..23caf0baa 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt @@ -111,12 +111,8 @@ class WgConfigAdapter(private val context: Context) : private var job: Job? = null fun update(config: WgConfigFiles) { - b.interfaceNameText.text = - context.getString( - R.string.about_version_install_source, - config.name.take(11), - config.id.toString() - ) + b.interfaceNameText.text = config.name.take(12) + b.interfaceIdText.text = context.getString(R.string.single_argument_parenthesis, config.id.toString()) b.interfaceSwitch.isChecked = config.isActive && VpnController.hasTunnel() setupClickListeners(config) updateStatusJob(config) diff --git a/app/src/main/res/layout/list_item_wg_general_interface.xml b/app/src/main/res/layout/list_item_wg_general_interface.xml index 98a00c574..a3bae8511 100644 --- a/app/src/main/res/layout/list_item_wg_general_interface.xml +++ b/app/src/main/res/layout/list_item_wg_general_interface.xml @@ -41,18 +41,25 @@ android:layout_height="wrap_content" android:orientation="horizontal"> - + android:textAppearance="?attr/textAppearanceHeadline6" /> + + + android:textAppearance="?attr/textAppearanceHeadline6" /> + + Date: Mon, 8 Jul 2024 20:16:09 +0530 Subject: [PATCH 031/188] univ-firewall: display blocked count stats for each rule --- .../ui/activity/NetworkLogsActivity.kt | 14 + .../UniversalFirewallSettingsActivity.kt | 202 +++++++++ .../ui/fragment/ConnectionTrackerFragment.kt | 15 +- .../bravedns/ui/fragment/DnsLogFragment.kt | 5 + .../bravedns/database/ConnectionTrackerDAO.kt | 12 +- .../database/ConnectionTrackerRepository.kt | 4 + .../activity_universal_firewall_settings.xml | 420 +++++++++++++++++- 7 files changed, 666 insertions(+), 6 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/NetworkLogsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/NetworkLogsActivity.kt index 196941e92..323df4a7e 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/NetworkLogsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/NetworkLogsActivity.kt @@ -30,6 +30,7 @@ import com.celzero.bravedns.databinding.ActivityNetworkLogsBinding import com.celzero.bravedns.service.BraveVPNService import com.celzero.bravedns.service.PersistentState import com.celzero.bravedns.service.VpnController +import com.celzero.bravedns.ui.activity.UniversalFirewallSettingsActivity.Companion.RULES_SEARCH_ID import com.celzero.bravedns.ui.fragment.ConnectionTrackerFragment import com.celzero.bravedns.ui.fragment.DnsLogFragment import com.celzero.bravedns.ui.fragment.RethinkLogFragment @@ -42,6 +43,9 @@ class NetworkLogsActivity : AppCompatActivity(R.layout.activity_network_logs) { private val b by viewBinding(ActivityNetworkLogsBinding::bind) private var fragmentIndex = 0 private var searchParam = "" + // to handle search navigation from universal firewall, to show only the search results + // of the selected universal rule, show only network logs tab + private var isUnivNavigated = false private val persistentState by inject() private val appConfig by inject() @@ -57,6 +61,9 @@ class NetworkLogsActivity : AppCompatActivity(R.layout.activity_network_logs) { super.onCreate(savedInstanceState) fragmentIndex = intent.getIntExtra(Constants.VIEW_PAGER_SCREEN_TO_LOAD, 0) searchParam = intent.getStringExtra(Constants.SEARCH_QUERY) ?: "" + if (searchParam.contains(RULES_SEARCH_ID)) { + isUnivNavigated = true + } init() } @@ -99,6 +106,10 @@ class NetworkLogsActivity : AppCompatActivity(R.layout.activity_network_logs) { } private fun getCount(): Int { + if (isUnivNavigated) { + return 1 + } + var count = 0 if (persistentState.routeRethinkInRethink) { count = 1 @@ -111,6 +122,9 @@ class NetworkLogsActivity : AppCompatActivity(R.layout.activity_network_logs) { } private fun getFragment(position: Int): Fragment { + if (isUnivNavigated) { + return ConnectionTrackerFragment.newInstance(searchParam) + } return when (position) { 0 -> { if (appConfig.getBraveMode().isDnsMode()) { diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/UniversalFirewallSettingsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/UniversalFirewallSettingsActivity.kt index d900ca169..2542882b0 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/UniversalFirewallSettingsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/UniversalFirewallSettingsActivity.kt @@ -23,23 +23,41 @@ import android.content.Intent import android.content.res.Configuration import android.os.Bundle import android.provider.Settings +import android.view.View import android.widget.CompoundButton import android.widget.Toast import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope import by.kirich1409.viewbindingdelegate.viewBinding import com.celzero.bravedns.R +import com.celzero.bravedns.database.ConnectionTracker +import com.celzero.bravedns.database.ConnectionTrackerRepository import com.celzero.bravedns.databinding.ActivityUniversalFirewallSettingsBinding +import com.celzero.bravedns.service.FirewallRuleset import com.celzero.bravedns.service.PersistentState import com.celzero.bravedns.util.BackgroundAccessibilityService +import com.celzero.bravedns.util.Constants import com.celzero.bravedns.util.Themes import com.celzero.bravedns.util.Utilities import com.google.android.material.dialog.MaterialAlertDialogBuilder +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.koin.android.ext.android.inject class UniversalFirewallSettingsActivity : AppCompatActivity(R.layout.activity_universal_firewall_settings) { private val b by viewBinding(ActivityUniversalFirewallSettingsBinding::bind) private val persistentState by inject() + private val connTrackerRepository by inject() + + private lateinit var blockedUniversalRules : List + + companion object { + const val RULES_SEARCH_ID = "R:" + } override fun onCreate(savedInstanceState: Bundle?) { setTheme(Themes.getCurrentTheme(isDarkThemeOn(), persistentState.theme)) @@ -69,6 +87,7 @@ class UniversalFirewallSettingsActivity : b.firewallUnivLockdownCheck.isChecked = persistentState.getUniversalLockdown() setupClickListeners() + updateStats() } private fun setupClickListeners() { @@ -164,6 +183,25 @@ class UniversalFirewallSettingsActivity : b.firewallUnivLockdownTxt.setOnClickListener { b.firewallUnivLockdownCheck.isChecked = !b.firewallUnivLockdownCheck.isChecked } + + // click listener for the stats + b.firewallDeviceLockedRl.setOnClickListener { startActivity(FirewallRuleset.RULE3.id) } + + b.firewallNotInUseRl.setOnClickListener { startActivity(FirewallRuleset.RULE4.id) } + + b.firewallUnknownRl.setOnClickListener { startActivity(FirewallRuleset.RULE5.id) } + + b.firewallUdpRl.setOnClickListener { startActivity(FirewallRuleset.RULE6.id) } + + b.firewallDnsBypassRl.setOnClickListener { startActivity(FirewallRuleset.RULE7.id) } + + b.firewallNewAppRl.setOnClickListener { startActivity(FirewallRuleset.RULE8.id) } + + b.firewallMeteredRl.setOnClickListener { startActivity(FirewallRuleset.RULE1F.id) } + + b.firewallHttpRl.setOnClickListener { startActivity(FirewallRuleset.RULE10.id) } + + b.firewallLockdownRl.setOnClickListener { startActivity(FirewallRuleset.RULE11.id) } } private fun recheckFirewallBackgroundMode(isChecked: Boolean) { @@ -249,6 +287,18 @@ class UniversalFirewallSettingsActivity : } } + private var maxValue: Double = 0.0 + + private fun calculatePercentage(c: Double): Int { + if (maxValue == 0.0) return 0 + if (c > maxValue) { + maxValue = c + return 100 + } + val percentage = (c / maxValue) * 100 + return percentage.toInt() + } + private fun showPermissionAlert() { val builder = MaterialAlertDialogBuilder(this) builder.setTitle(R.string.alert_permission_accessibility) @@ -275,4 +325,156 @@ class UniversalFirewallSettingsActivity : Logger.e(LOG_TAG_FIREWALL, "Failure accessing accessibility settings: ${e.message}", e) } } + + private fun updateStats() { + io { + // get stats for all the firewall rules + // update the UI with the stats + // 1. device locked - 2. background mode - 3. unknown 4. udp 5. dns bypass 6. new app 7. + // metered 8. http 9. universal lockdown + // instead get all the stats in one go and update the UI + blockedUniversalRules = connTrackerRepository.getBlockedUniversalRulesCount() + val deviceLocked = + blockedUniversalRules.filter { it.blockedByRule.contains(FirewallRuleset.RULE3.id) } + val backgroundMode = + blockedUniversalRules.filter { it.blockedByRule.contains(FirewallRuleset.RULE4.id) } + val unknown = + blockedUniversalRules.filter { it.blockedByRule.contains(FirewallRuleset.RULE5.id) } + val udp = + blockedUniversalRules.filter { it.blockedByRule.contains(FirewallRuleset.RULE6.id) } + val dnsBypass = + blockedUniversalRules.filter { it.blockedByRule.contains(FirewallRuleset.RULE7.id) } + val newApp = + blockedUniversalRules.filter { it.blockedByRule.contains(FirewallRuleset.RULE8.id) } + val metered = + blockedUniversalRules.filter { + it.blockedByRule.contains(FirewallRuleset.RULE1F.id) + } + val http = + blockedUniversalRules.filter { + it.blockedByRule.contains(FirewallRuleset.RULE10.id) + } + val universalLockdown = + blockedUniversalRules.filter { + it.blockedByRule.contains(FirewallRuleset.RULE11.id) + } + + val blockedCountList = + listOf( + deviceLocked.size, + backgroundMode.size, + unknown.size, + udp.size, + dnsBypass.size, + newApp.size, + metered.size, + http.size, + universalLockdown.size + ) + + maxValue = blockedCountList.maxOrNull()?.toDouble() ?: 0.0 + + uiCtx { + b.firewallDeviceLockedShimmerLayout.postDelayed( + { + if (!canPerformUiAction()) return@postDelayed + + stopShimmer() + hideShimmer() + + b.deviceLockedProgress.progress = + calculatePercentage(blockedCountList[0].toDouble()) + b.notInUseProgress.progress = + calculatePercentage(blockedCountList[1].toDouble()) + b.unknownProgress.progress = + calculatePercentage(blockedCountList[2].toDouble()) + b.udpProgress.progress = calculatePercentage(blockedCountList[3].toDouble()) + b.dnsBypassProgress.progress = + calculatePercentage(blockedCountList[4].toDouble()) + b.newAppProgress.progress = + calculatePercentage(blockedCountList[5].toDouble()) + b.meteredProgress.progress = + calculatePercentage(blockedCountList[6].toDouble()) + b.httpProgress.progress = + calculatePercentage(blockedCountList[7].toDouble()) + b.lockdownProgress.progress = + calculatePercentage(blockedCountList[8].toDouble()) + + b.firewallDeviceLockedStats.text = deviceLocked.size.toString() + b.firewallNotInUseStats.text = backgroundMode.size.toString() + b.firewallUnknownStats.text = unknown.size.toString() + b.firewallUdpStats.text = udp.size.toString() + b.firewallDnsBypassStats.text = dnsBypass.size.toString() + b.firewallNewAppStats.text = newApp.size.toString() + b.firewallMeteredStats.text = metered.size.toString() + b.firewallHttpStats.text = http.size.toString() + b.firewallLockdownStats.text = universalLockdown.size.toString() + }, + 500 + ) + } + } + } + + private fun canPerformUiAction(): Boolean { + return !isFinishing && + !isDestroyed && + lifecycle.currentState.isAtLeast(Lifecycle.State.INITIALIZED) && + !isChangingConfigurations + } + + override fun onPause() { + super.onPause() + stopShimmer() + } + + private fun stopShimmer() { + if (!canPerformUiAction()) return + + b.firewallUdpShimmerLayout.stopShimmer() + b.firewallDeviceLockedShimmerLayout.stopShimmer() + b.firewallNotInUseShimmerLayout.stopShimmer() + b.firewallUnknownShimmerLayout.stopShimmer() + b.firewallDnsBypassShimmerLayout.stopShimmer() + b.firewallNewAppShimmerLayout.stopShimmer() + b.firewallMeteredShimmerLayout.stopShimmer() + b.firewallHttpShimmerLayout.stopShimmer() + b.firewallLockdownShimmerLayout.stopShimmer() + } + + private fun hideShimmer() { + if (!canPerformUiAction()) return + + b.firewallUdpShimmerLayout.visibility = View.GONE + b.firewallDeviceLockedShimmerLayout.visibility = View.GONE + b.firewallNotInUseShimmerLayout.visibility = View.GONE + b.firewallUnknownShimmerLayout.visibility = View.GONE + b.firewallDnsBypassShimmerLayout.visibility = View.GONE + b.firewallNewAppShimmerLayout.visibility = View.GONE + b.firewallMeteredShimmerLayout.visibility = View.GONE + b.firewallHttpShimmerLayout.visibility = View.GONE + b.firewallLockdownShimmerLayout.visibility = View.GONE + } + + private fun startActivity(rule: String?) { + if (rule.isNullOrEmpty()) return + + // if the rules are not blocked, then no need to start the activity + val size = blockedUniversalRules.filter { it.blockedByRule.contains(rule) }.size + if (size == 0) return + + val intent = Intent(this, NetworkLogsActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + val searchParam = RULES_SEARCH_ID + rule + intent.putExtra(Constants.SEARCH_QUERY, searchParam) + startActivity(intent) + } + + private fun io(f: suspend () -> Unit): Job { + return lifecycleScope.launch(Dispatchers.IO) { f() } + } + + private suspend fun uiCtx(f: suspend () -> Unit) { + withContext(Dispatchers.Main) { f() } + } } diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/ConnectionTrackerFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/ConnectionTrackerFragment.kt index 3d1d234b7..39faebe96 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/ConnectionTrackerFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/ConnectionTrackerFragment.kt @@ -34,6 +34,7 @@ import com.celzero.bravedns.database.ConnectionTrackerRepository import com.celzero.bravedns.databinding.ActivityConnectionTrackerBinding import com.celzero.bravedns.service.FirewallRuleset import com.celzero.bravedns.service.PersistentState +import com.celzero.bravedns.ui.activity.UniversalFirewallSettingsActivity import com.celzero.bravedns.util.Constants import com.celzero.bravedns.util.UIUtils.formatToRelativeTime import com.celzero.bravedns.util.Utilities @@ -77,7 +78,15 @@ class ConnectionTrackerFragment : initView() if (arguments != null) { val query = arguments?.getString(Constants.SEARCH_QUERY) ?: return - b.connectionSearch.setQuery(query, true) + if (query.contains(UniversalFirewallSettingsActivity.RULES_SEARCH_ID)) { + val rule = query.split(UniversalFirewallSettingsActivity.RULES_SEARCH_ID)[1] + filterCategories.add(rule) + filterType = TopLevelFilter.BLOCKED + viewModel.setFilter(filterQuery, filterCategories, filterType) + hideSearchLayout() + } else { + b.connectionSearch.setQuery(query, true) + } } } @@ -123,6 +132,10 @@ class ConnectionTrackerFragment : remakeChildFilterChipsUi(FirewallRuleset.getBlockedRules()) } + private fun hideSearchLayout() { + b.connectionCardViewTop.visibility = View.GONE + } + override fun onResume() { super.onResume() b.connectionListRl.requestFocus() diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/DnsLogFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/DnsLogFragment.kt index bef66da2b..76e08ee60 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/DnsLogFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/DnsLogFragment.kt @@ -33,6 +33,7 @@ import com.celzero.bravedns.adapter.DnsQueryAdapter import com.celzero.bravedns.database.DnsLogRepository import com.celzero.bravedns.databinding.FragmentDnsLogsBinding import com.celzero.bravedns.service.PersistentState +import com.celzero.bravedns.ui.activity.UniversalFirewallSettingsActivity import com.celzero.bravedns.util.Constants import com.celzero.bravedns.util.UIUtils.formatToRelativeTime import com.celzero.bravedns.util.Utilities @@ -80,6 +81,10 @@ class DnsLogFragment : Fragment(R.layout.fragment_dns_logs), SearchView.OnQueryT initView() if (arguments != null) { val query = arguments?.getString(Constants.SEARCH_QUERY) ?: return + if (query.contains(UniversalFirewallSettingsActivity.RULES_SEARCH_ID)) { + // do nothing, as the search is for the firewall rules and not for the dns + return + } b.queryListSearch.setQuery(query, true) } } diff --git a/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerDAO.kt b/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerDAO.kt index 01ce244e8..01d2555fb 100644 --- a/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerDAO.kt +++ b/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerDAO.kt @@ -89,17 +89,17 @@ interface ConnectionTrackerDAO { fun getBlockedConnections(query: String): PagingSource @Query( - "SELECT uid, ipAddress, port, COUNT(ipAddress) as count, flag as flag, 0 as blocked, GROUP_CONCAT(DISTINCT dnsQuery) as appOrDnsName FROM ConnectionTracker WHERE uid = :uid and timeStamp > :to GROUP BY ipAddress, uid, port ORDER BY count DESC" + "SELECT uid, ipAddress, port, COUNT(ipAddress) as count, flag as flag, 0 as blocked, GROUP_CONCAT(DISTINCT dnsQuery) as appOrDnsName FROM ConnectionTracker WHERE uid = :uid and timeStamp > :to GROUP BY uid, ipAddress, port ORDER BY count DESC" ) fun getAppIpLogs(uid: Int, to: Long): PagingSource @Query( - "SELECT uid, ipAddress, port, COUNT(ipAddress) as count, flag as flag, 0 as blocked, '' as appOrDnsName FROM ConnectionTracker WHERE uid = :uid and timeStamp > :to GROUP BY ipAddress, uid, port ORDER BY count DESC LIMIT 3" + "SELECT uid, ipAddress, port, COUNT(ipAddress) as count, flag as flag, 0 as blocked, '' as appOrDnsName FROM ConnectionTracker WHERE uid = :uid and timeStamp > :to GROUP BY uid, ipAddress, port ORDER BY count DESC LIMIT 3" ) fun getAppIpLogsLimited(uid: Int, to: Long): PagingSource @Query( - "SELECT uid, ipAddress, port, COUNT(ipAddress) as count, flag as flag, 0 as blocked, GROUP_CONCAT(DISTINCT dnsQuery) as appOrDnsName FROM ConnectionTracker WHERE uid = :uid and timeStamp > :to and ipAddress like :query GROUP BY ipAddress, uid, port ORDER BY count DESC" + "SELECT uid, ipAddress, port, COUNT(ipAddress) as count, flag as flag, 0 as blocked, GROUP_CONCAT(DISTINCT dnsQuery) as appOrDnsName FROM ConnectionTracker WHERE uid = :uid and timeStamp > :to and ipAddress like :query GROUP BY uid, ipAddress, port ORDER BY count DESC" ) fun getAppIpLogsFiltered(uid: Int, to: Long, query: String): PagingSource @@ -160,6 +160,9 @@ interface ConnectionTrackerDAO { @Query("delete from ConnectionTracker where uid = :uid") fun clearLogsByUid(uid: Int) + @Query("delete from ConnectionTracker where uid = :uid and timeStamp > :time") + fun clearLogsByTime(uid: Int, time: Long) + @Query("DELETE FROM ConnectionTracker WHERE timeStamp < :date") fun purgeLogsByDate(date: Long) @Query( @@ -298,4 +301,7 @@ interface ConnectionTrackerDAO { "select sum(downloadBytes) as totalDownload, sum(uploadBytes) as totalUpload, count(id) as connectionsCount, ict.meteredDataUsage as meteredDataUsage from ConnectionTracker as ct join (select sum(downloadBytes + uploadBytes) as meteredDataUsage from ConnectionTracker where connType like :meteredTxt and timeStamp > :to) as ict where timeStamp > :to" ) fun getTotalUsages(to: Long, meteredTxt: String): DataUsageSummary + + @Query("select * from ConnectionTracker where blockedByRule in ('Rule #1B', 'Rule #1F', 'Rule #3', 'Rule #4', 'Rule #5', 'Rule #6', 'Http block', 'Universal Lockdown')") + fun getBlockedUniversalRulesCount(): List } diff --git a/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerRepository.kt b/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerRepository.kt index 7e79e3b0b..94b010597 100644 --- a/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerRepository.kt +++ b/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerRepository.kt @@ -82,4 +82,8 @@ class ConnectionTrackerRepository(private val connectionTrackerDAO: ConnectionTr suspend fun getDataUsage(before: Long, current: Long): List { return connectionTrackerDAO.getDataUsage(before, current) } + + suspend fun getBlockedUniversalRulesCount(): List { + return connectionTrackerDAO.getBlockedUniversalRulesCount() + } } diff --git a/app/src/main/res/layout/activity_universal_firewall_settings.xml b/app/src/main/res/layout/activity_universal_firewall_settings.xml index 7d72d03d2..c27f05484 100644 --- a/app/src/main/res/layout/activity_universal_firewall_settings.xml +++ b/app/src/main/res/layout/activity_universal_firewall_settings.xml @@ -10,7 +10,6 @@ app:layout_behavior="@string/appbar_scrolling_view_behavior" tools:context=".ui.fragment.FirewallSettingsFragment"> - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:layout_marginBottom="2dp"> + + + + + + + + + + + + + + From 840022586ffaf9ccbf9cb93e7fb9b2e997ba1fb2 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 8 Jul 2024 20:16:53 +0530 Subject: [PATCH 032/188] wg-ui: move onBackPressed callback to onCreate --- .../bravedns/ui/activity/WgMainActivity.kt | 22 +++++++++++++------ 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/WgMainActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/WgMainActivity.kt index ff2f72d76..3368ba77d 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/WgMainActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/WgMainActivity.kt @@ -24,6 +24,7 @@ import android.content.res.Configuration import android.os.Bundle import android.view.View import android.widget.Toast +import androidx.activity.OnBackPressedCallback import androidx.activity.addCallback import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity @@ -152,6 +153,19 @@ class WgMainActivity : setTheme(Themes.getCurrentTheme(isDarkThemeOn(), persistentState.theme)) super.onCreate(savedInstanceState) init() + onBackPressedDispatcher.addCallback( + this /* lifecycle owner */, + object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + if (b.createFab.visibility == View.VISIBLE) { + collapseFab() + } else { + finish() + } + return + } + } + ) } private fun init() { @@ -161,13 +175,7 @@ class WgMainActivity : observeDnsName() setupClickListeners() - onBackPressedDispatcher.addCallback(this /* lifecycle owner */) { - if (b.createFab.visibility == View.VISIBLE) { - collapseFab() - } else { - finish() - } - } + } private fun setAdapter() { From 829cc4f0aa389c921d49621e4533680fee16252b Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 8 Jul 2024 20:21:45 +0530 Subject: [PATCH 033/188] proxy-ui: show proxy ID in IP log bottom sheet screen --- .../ui/bottomsheet/ConnTrackerBottomSheet.kt | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/ConnTrackerBottomSheet.kt b/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/ConnTrackerBottomSheet.kt index 4c55004e4..2aea7bc8c 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/ConnTrackerBottomSheet.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/ConnTrackerBottomSheet.kt @@ -35,6 +35,7 @@ import android.widget.Toast import androidx.appcompat.widget.AppCompatImageView import androidx.core.content.ContextCompat import androidx.lifecycle.lifecycleScope +import backend.Backend import com.celzero.bravedns.R import com.celzero.bravedns.adapter.FirewallStatusSpinnerAdapter import com.celzero.bravedns.data.ConnectionRules @@ -226,11 +227,15 @@ class ConnTrackerBottomSheet : BottomSheetDialogFragment(), KoinComponent { } val rule = info!!.blockedByRule + val skipProxyList = listOf(Backend.Base, Backend.Exit, Backend.Block) // TODO: below code is not required, remove it in future (20/03/2023) if (rule.contains(FirewallRuleset.RULE2G.id)) { b.bsConnTrackAppInfo.text = getFirewallRule(FirewallRuleset.RULE2G.id)?.title?.let { getString(it) } return + } else if (!info?.proxyDetails.isNullOrEmpty() && !skipProxyList.contains(info?.proxyDetails)) { + // add the proxy id to the chip text if available + b.bsConnTrackAppInfo.text = getString(R.string.two_argument_colon, getFirewallRule(rule)?.title?.let { getString(it) }, info?.proxyDetails) } else { b.bsConnTrackAppInfo.text = getFirewallRule(rule)?.title?.let { getString(it) } } @@ -276,7 +281,15 @@ class ConnTrackerBottomSheet : BottomSheetDialogFragment(), KoinComponent { private fun displaySummaryDetails() { b.bsConnConnTypeSecondary.visibility = View.GONE - b.connectionMessage.text = info?.message + // show connId and message if the log level is less than DEBUG + if (Logger.LoggerType.fromId(persistentState.goLoggerLevel.toInt()) + .isLessThan(Logger.LoggerType.DEBUG) + ) { + b.connectionMessage.text = + requireContext().getString(R.string.two_argument_colon, info?.connId, info?.message) + } else { + b.connectionMessage.text = info?.message + } if (VpnController.hasCid(info!!.connId, info!!.uid)) { b.connectionMessageLl.visibility = View.VISIBLE From aeab96e9fecc7017b3533a9fea8116e4b4120120 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 8 Jul 2024 20:22:12 +0530 Subject: [PATCH 034/188] ktfmt: doh adapter --- .../java/com/celzero/bravedns/adapter/DohEndpointAdapter.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/DohEndpointAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/DohEndpointAdapter.kt index f2b2f316f..9144ccd1b 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/DohEndpointAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/DohEndpointAdapter.kt @@ -207,12 +207,10 @@ class DohEndpointAdapter(private val context: Context, private val appConfig: Ap builder.setTitle(title) builder.setMessage(url + "\n\n" + getDnsDesc(message)) builder.setCancelable(true) - builder.setPositiveButton(context.getString(R.string.dns_info_positive)) { dialogInterface, - _ -> + builder.setPositiveButton(context.getString(R.string.dns_info_positive)) { dialogInterface, _ -> dialogInterface.dismiss() } - builder.setNeutralButton(context.getString(R.string.dns_info_neutral)) { _: DialogInterface, - _: Int -> + builder.setNeutralButton(context.getString(R.string.dns_info_neutral)) { _: DialogInterface, _: Int -> clipboardCopy(context, url, context.getString(R.string.copy_clipboard_label)) Utilities.showToastUiCentered( context, From 89284901412b3822d98f6731d5255118d8dd4b4f Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 8 Jul 2024 20:23:07 +0530 Subject: [PATCH 035/188] rethink-log-ui: correct DIFF_UTIL for log adapter --- .../full/java/com/celzero/bravedns/adapter/RethinkLogAdapter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/RethinkLogAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/RethinkLogAdapter.kt index 60d6f449e..28731138f 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/RethinkLogAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/RethinkLogAdapter.kt @@ -66,7 +66,7 @@ class RethinkLogAdapter(private val context: Context) : override fun areContentsTheSame( oldConnection: RethinkLog, newConnection: RethinkLog - ) = oldConnection == newConnection + ) = oldConnection.id == newConnection.id } private const val MAX_BYTES = 500000 // 500 KB From d6eabdd500c74acca9f25ca92e214c829e8e748b Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 8 Jul 2024 20:23:57 +0530 Subject: [PATCH 036/188] logger: add content to zip at once using StringBuilder --- .../bravedns/scheduler/LogExportWorker.kt | 46 +++++++++++++------ 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/scheduler/LogExportWorker.kt b/app/src/full/java/com/celzero/bravedns/scheduler/LogExportWorker.kt index d64c6c767..547c94e98 100644 --- a/app/src/full/java/com/celzero/bravedns/scheduler/LogExportWorker.kt +++ b/app/src/full/java/com/celzero/bravedns/scheduler/LogExportWorker.kt @@ -1,3 +1,18 @@ +/* + * Copyright 2024 RethinkDNS and its authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.celzero.bravedns.scheduler import Logger @@ -15,13 +30,16 @@ import java.util.zip.ZipOutputStream import org.koin.core.component.KoinComponent import org.koin.core.component.inject import java.io.BufferedOutputStream -import java.util.zip.ZipFile class LogExportWorker(context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams), KoinComponent { private val consoleLogDao by inject() + companion object { + private const val query = "SELECT * FROM ConsoleLog order by id" + } + override suspend fun doWork(): Result { return try { val filePath = inputData.getString("filePath") ?: return Result.failure() @@ -36,7 +54,7 @@ class LogExportWorker(context: Context, workerParams: WorkerParameters) : private fun exportLogsToCsvStream(filePath: String): Boolean { var cursor: Cursor? = null try { - val query = SimpleSQLiteQuery("SELECT * FROM ConsoleLog order by id") + val query = SimpleSQLiteQuery(query) cursor = consoleLogDao.getLogsCursor(query) val file = File(filePath) @@ -45,19 +63,21 @@ class LogExportWorker(context: Context, workerParams: WorkerParameters) : file.delete() } + val stringBuilder = StringBuilder() + cursor?.let { + if (it.moveToFirst()) { + do { + val timestamp = it.getLong(it.getColumnIndexOrThrow("timestamp")) + val message = it.getString(it.getColumnIndexOrThrow("message")) + stringBuilder.append("$timestamp,$message\n") + } while (it.moveToNext()) + } + } + ZipOutputStream(BufferedOutputStream(FileOutputStream(filePath))).use { zos -> val zipEntry = ZipEntry("log_${System.currentTimeMillis()}.txt") zos.putNextEntry(zipEntry) - if (cursor.moveToFirst()) { - do { - val timestamp = - cursor.getLong(cursor.getColumnIndexOrThrow("timestamp")) - val message = - cursor.getString(cursor.getColumnIndexOrThrow("message")) - val logEntry = "$timestamp,$message\n" - zos.write(logEntry.toByteArray()) - } while (cursor.moveToNext()) - } + zos.write(stringBuilder.toString().toByteArray()) zos.closeEntry() } @@ -65,7 +85,7 @@ class LogExportWorker(context: Context, workerParams: WorkerParameters) : return true } catch (e: Exception) { Logger.e(LOG_TAG_BUG_REPORT, "Error exporting logs", e) - } finally{ + } finally { cursor?.close() } return false From d4348a5c9ae3e407a26c59cd496eeaa413b8d036 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 8 Jul 2024 20:28:30 +0530 Subject: [PATCH 037/188] ui: show netstat in about and home screen add netstat with trafficstat in home screen with delay of 2.5s rearrange about screen-> contact section show region if available in home screen->dns tab --- .../bravedns/ui/fragment/AboutFragment.kt | 47 ++++++++++++++ .../ui/fragment/HomeScreenFragment.kt | 63 ++++++++++++++++--- .../java/com/celzero/bravedns/util/UIUtils.kt | 16 +++++ .../full/res/layout/fragment_home_screen.xml | 24 +++++-- app/src/main/res/layout/fragment_about.xml | 47 +++++++++----- 5 files changed, 170 insertions(+), 27 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/AboutFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/AboutFragment.kt index 18acefe0c..15a261885 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/AboutFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/AboutFragment.kt @@ -51,9 +51,11 @@ import com.celzero.bravedns.scheduler.BugReportZipper.getZipFileName import com.celzero.bravedns.scheduler.EnhancedBugReport import com.celzero.bravedns.scheduler.WorkScheduler import com.celzero.bravedns.service.AppUpdater +import com.celzero.bravedns.service.VpnController import com.celzero.bravedns.ui.HomeScreenActivity import com.celzero.bravedns.util.Constants.Companion.INIT_TIME_MS import com.celzero.bravedns.util.Constants.Companion.RETHINKDNS_SPONSOR_LINK +import com.celzero.bravedns.util.UIUtils import com.celzero.bravedns.util.UIUtils.openAppInfo import com.celzero.bravedns.util.UIUtils.openVpnProfile import com.celzero.bravedns.util.UIUtils.sendEmailIntent @@ -117,6 +119,7 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener, K b.aboutAppVersion.setOnClickListener(this) b.aboutAppContributors.setOnClickListener(this) b.aboutAppTranslate.setOnClickListener(this) + b.aboutStats.setOnClickListener(this) try { val version = getVersionName() ?: "" @@ -226,6 +229,50 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener, K b.aboutElement -> { openActionViewIntent(getString(R.string.about_matrix_handle).toUri()) } + b.aboutStats -> { + openStatsDialog() + } + } + } + + private fun openStatsDialog() { + io { + val stat = VpnController.getNetStat() + val formatedStat = UIUtils.formatNetStat(stat) + uiCtx { + val dialogBinding = DialogInfoRulesLayoutBinding.inflate(layoutInflater) + val builder = + MaterialAlertDialogBuilder(requireContext()).setView(dialogBinding.root) + val lp = WindowManager.LayoutParams() + val dialog = builder.create() + dialog.show() + lp.copyFrom(dialog.window?.attributes) + lp.width = WindowManager.LayoutParams.MATCH_PARENT + lp.height = WindowManager.LayoutParams.WRAP_CONTENT + + dialog.setCancelable(true) + dialog.window?.attributes = lp + + val heading = dialogBinding.infoRulesDialogRulesTitle + val okBtn = dialogBinding.infoRulesDialogCancelImg + val descText = dialogBinding.infoRulesDialogRulesDesc + dialogBinding.infoRulesDialogRulesIcon.visibility = View.GONE + + heading.text = "Network Stats" + heading.setCompoundDrawablesWithIntrinsicBounds( + ContextCompat.getDrawable(requireContext(), R.drawable.ic_log_level), + null, + null, + null + ) + + descText.movementMethod = LinkMovementMethod.getInstance() + descText.text = formatedStat + + okBtn.setOnClickListener { dialog.dismiss() } + + dialog.show() + } } } diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt index fa8b769bd..8be701021 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt @@ -39,6 +39,7 @@ import android.provider.Settings import android.text.format.DateUtils import android.util.TypedValue import android.view.View +import android.view.animation.AnimationUtils import android.widget.Toast import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResultLauncher @@ -125,6 +126,7 @@ class HomeScreenFragment : Fragment(R.layout.fragment_home_screen) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + initializeValues() initializeClickListeners() observeVpnState() @@ -406,10 +408,10 @@ class HomeScreenFragment : Fragment(R.layout.fragment_home_screen) { val status = VpnController.getProxyStatusById(proxyId) if (status != null) { // consider starting and up as active - if (status == Backend.TOK || status == Backend.TUP || status == Backend.TZZ) { - active++ - } else { + if (status == Backend.TKO) { failing++ + } else { + active++ } } else { failing++ @@ -526,6 +528,12 @@ class HomeScreenFragment : Fragment(R.layout.fragment_home_screen) { appConfig.getConnectedDnsObservable().observe(viewLifecycleOwner) { updateUiWithDnsStates(it) } + + VpnController.getRegionLiveData().observe(viewLifecycleOwner) { + if (it != null) { + b.fhsCardRegion.text = it + } + } } private fun updateUiWithDnsStates(dnsName: String) { @@ -554,7 +562,7 @@ class HomeScreenFragment : Fragment(R.layout.fragment_home_screen) { failing = false if (isAdded) { b.fhsCardDnsLatency.visibility = View.VISIBLE - b.fhsCardDnsFailure.visibility = View.GONE + b.fhsCardDnsFailure.visibility = View.INVISIBLE } return@ui } @@ -563,7 +571,7 @@ class HomeScreenFragment : Fragment(R.layout.fragment_home_screen) { failing = true } if (failing && isAdded) { - b.fhsCardDnsLatency.visibility = View.GONE + b.fhsCardDnsLatency.visibility = View.INVISIBLE b.fhsCardDnsFailure.visibility = View.VISIBLE b.fhsCardDnsFailure.text = getString(R.string.failed_using_default) } @@ -621,6 +629,7 @@ class HomeScreenFragment : Fragment(R.layout.fragment_home_screen) { private fun unobserveDnsStates() { persistentState.median.removeObservers(viewLifecycleOwner) appConfig.getConnectedDnsObservable().removeObservers(viewLifecycleOwner) + VpnController.getRegionLiveData().removeObservers(viewLifecycleOwner) } private fun observeUniversalStates() { @@ -926,15 +935,52 @@ class HomeScreenFragment : Fragment(R.layout.fragment_home_screen) { private fun startTrafficStats() { trafficStatsTicker = ui("trafficStatsTicker") { + var counter = 0 while (true) { - fetchTrafficStats() - kotlinx.coroutines.delay(1500L) + if (counter % 2 == 0) { + fetchTrafficStats() + } else { + fetchNetStats() + } + kotlinx.coroutines.delay(2500L) + counter++ } } } + private fun fetchNetStats() { + val stat = VpnController.getNetStat() + val nic = stat?.nic() + + // show the stats in MB/s + val txBytes = String.format("%.2f", (nic?.txBytes ?: 0) / 1000000.0) + val rxBytes = String.format("%.2f", (nic?.rxBytes ?: 0) / 1000000.0) + + b.fhsInternetSpeed.visibility = View.VISIBLE + b.fhsInternetSpeedUnit.visibility = View.VISIBLE + b.fhsInternetSpeed.text = + getString( + R.string.two_argument_space, + getString( + R.string.two_argument_space, + txBytes, + getString(R.string.symbol_black_up) + ), + getString( + R.string.two_argument_space, + rxBytes, + getString(R.string.symbol_black_down) + ) + ) + b.fhsInternetSpeedUnit.text = getString(R.string.symbol_mbs) + } + private fun stopTrafficStats() { - trafficStatsTicker.cancel() + try { + trafficStatsTicker.cancel() + } catch (e: Exception) { + Logger.e(LOG_TAG_VPN, "error stopping traffic stats ticker", e) + } } data class TxRx( @@ -982,6 +1028,7 @@ class HomeScreenFragment : Fragment(R.layout.fragment_home_screen) { getString(R.string.symbol_black_down) ) ) + b.fhsInternetSpeedUnit.text = getString(R.string.symbol_kbs) } /** diff --git a/app/src/full/java/com/celzero/bravedns/util/UIUtils.kt b/app/src/full/java/com/celzero/bravedns/util/UIUtils.kt index 956a9169d..16f6d09da 100644 --- a/app/src/full/java/com/celzero/bravedns/util/UIUtils.kt +++ b/app/src/full/java/com/celzero/bravedns/util/UIUtils.kt @@ -632,4 +632,20 @@ object UIUtils { return result.toString().trim() } + + fun formatNetStat(stat: backend.NetStat?): String { + val ip = stat?.ip()?.toString() + val udp = stat?.udp()?.toString() + val tcp = stat?.tcp()?.toString() + val fwd = stat?.fwd()?.toString() + val icmp = stat?.icmp()?.toString() + val nic = stat?.nic()?.toString() + + var stats = nic + fwd + ip + icmp + tcp + udp + stats = stats.replace("{", "\n") + stats = stats.replace("}", "\n\n") + stats = stats.replace(",", "\n") + + return stats + } } diff --git a/app/src/full/res/layout/fragment_home_screen.xml b/app/src/full/res/layout/fragment_home_screen.xml index bdd2bde65..1fb0380a1 100644 --- a/app/src/full/res/layout/fragment_home_screen.xml +++ b/app/src/full/res/layout/fragment_home_screen.xml @@ -112,13 +112,26 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/fhs_card_dns_title" - android:layout_marginStart="7dp" + android:layout_marginStart="10dp" android:layout_marginTop="10dp" android:ellipsize="marquee" android:marqueeRepeatLimit="marquee_forever" android:singleLine="true" android:textSize="@dimen/extra_large_font_text_view" /> + + + android:maxLines="1" + android:paddingBottom="5dp" + android:singleLine="true" + android:textSize="@dimen/small_font_text_view" /> diff --git a/app/src/main/res/layout/fragment_about.xml b/app/src/main/res/layout/fragment_about.xml index 3b52f8fa5..72b9eae23 100644 --- a/app/src/main/res/layout/fragment_about.xml +++ b/app/src/main/res/layout/fragment_about.xml @@ -451,38 +451,39 @@ android:textStyle="bold" /> + + + + + Date: Mon, 8 Jul 2024 20:30:03 +0530 Subject: [PATCH 038/188] ui: new drawable icons for v055o --- app/src/main/res/drawable/ic_app_log.xml | 10 ++++ app/src/main/res/drawable/ic_info.xml | 46 +++++++++---------- app/src/main/res/drawable/ic_info_white.xml | 36 +++++++-------- .../main/res/drawable/ic_info_white_16.xml | 27 +++++++++++ app/src/main/res/drawable/ic_log_level.xml | 16 +++++++ app/src/main/res/drawable/ic_share.xml | 9 ++++ 6 files changed, 103 insertions(+), 41 deletions(-) create mode 100644 app/src/main/res/drawable/ic_app_log.xml create mode 100644 app/src/main/res/drawable/ic_info_white_16.xml create mode 100644 app/src/main/res/drawable/ic_log_level.xml create mode 100644 app/src/main/res/drawable/ic_share.xml diff --git a/app/src/main/res/drawable/ic_app_log.xml b/app/src/main/res/drawable/ic_app_log.xml new file mode 100644 index 000000000..a757f49ce --- /dev/null +++ b/app/src/main/res/drawable/ic_app_log.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_info.xml b/app/src/main/res/drawable/ic_info.xml index 045e8306e..0699ad2d3 100644 --- a/app/src/main/res/drawable/ic_info.xml +++ b/app/src/main/res/drawable/ic_info.xml @@ -1,27 +1,27 @@ - - - + android:viewportWidth="512" + android:viewportHeight="512"> + + + + diff --git a/app/src/main/res/drawable/ic_info_white.xml b/app/src/main/res/drawable/ic_info_white.xml index 83726c60e..5c06702b8 100644 --- a/app/src/main/res/drawable/ic_info_white.xml +++ b/app/src/main/res/drawable/ic_info_white.xml @@ -1,27 +1,27 @@ + android:viewportWidth="512" + android:viewportHeight="512"> + android:fillColor="?attr/svgFillColor" + android:pathData="m248,64c-101.61,0 -184,82.39 -184,184s82.39,184 184,184 184,-82.39 184,-184 -82.39,-184 -184,-184z" + android:strokeWidth="32" + android:strokeColor="?attr/svgStrokeColor" /> + android:strokeLineCap="round" + android:strokeLineJoin="round" /> + android:strokeLineCap="round" /> + diff --git a/app/src/main/res/drawable/ic_info_white_16.xml b/app/src/main/res/drawable/ic_info_white_16.xml new file mode 100644 index 000000000..6808fd15d --- /dev/null +++ b/app/src/main/res/drawable/ic_info_white_16.xml @@ -0,0 +1,27 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_log_level.xml b/app/src/main/res/drawable/ic_log_level.xml new file mode 100644 index 000000000..23eeeaeab --- /dev/null +++ b/app/src/main/res/drawable/ic_log_level.xml @@ -0,0 +1,16 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_share.xml b/app/src/main/res/drawable/ic_share.xml new file mode 100644 index 000000000..3b9a1ac48 --- /dev/null +++ b/app/src/main/res/drawable/ic_share.xml @@ -0,0 +1,9 @@ + + + From 91115447cf5b0f074ebf32e11e73f040fd4f5005 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 8 Jul 2024 20:41:16 +0530 Subject: [PATCH 039/188] logger-ui: console log ui changes --- .../ui/activity/ConsoleLogActivity.kt | 222 ++++++++++++++++-- .../bravedns/viewmodel/ConsoleLogViewModel.kt | 11 +- .../full/res/layout/activity_console_log.xml | 25 +- .../bravedns/database/ConsoleLogDAO.kt | 18 +- .../bravedns/database/ConsoleLogRepository.kt | 7 +- .../bravedns/service/ConsoleLogManager.kt | 5 +- .../main/res/layout/list_item_console_log.xml | 3 +- 7 files changed, 250 insertions(+), 41 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/ConsoleLogActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/ConsoleLogActivity.kt index 3c92f0ba1..5307889c6 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/ConsoleLogActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/ConsoleLogActivity.kt @@ -25,16 +25,26 @@ import android.content.res.Configuration import android.net.Uri import android.os.Bundle import android.os.Environment +import android.os.Handler +import android.os.Looper import android.provider.Settings +import android.text.method.LinkMovementMethod import android.view.View +import android.view.WindowManager import android.widget.Toast +import androidx.activity.OnBackPressedCallback +import androidx.activity.addCallback import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.content.FileProvider +import androidx.lifecycle.Lifecycle import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.work.WorkInfo import androidx.work.WorkManager @@ -42,16 +52,21 @@ import by.kirich1409.viewbindingdelegate.viewBinding import com.celzero.bravedns.R import com.celzero.bravedns.adapter.ConsoleLogAdapter import com.celzero.bravedns.databinding.ActivityConsoleLogBinding +import com.celzero.bravedns.databinding.DialogInfoRulesLayoutBinding import com.celzero.bravedns.scheduler.WorkScheduler import com.celzero.bravedns.service.PersistentState +import com.celzero.bravedns.service.VpnController import com.celzero.bravedns.util.Constants -import com.celzero.bravedns.util.CustomLinearLayoutManager import com.celzero.bravedns.util.Themes +import com.celzero.bravedns.util.UIUtils import com.celzero.bravedns.util.Utilities import com.celzero.bravedns.util.Utilities.isAtleastR +import com.celzero.bravedns.util.Utilities.showToastUiCentered import com.celzero.bravedns.viewmodel.ConsoleLogViewModel +import com.google.android.material.dialog.MaterialAlertDialogBuilder import java.io.File import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import org.koin.android.ext.android.inject @@ -88,7 +103,7 @@ class ConsoleLogActivity : AppCompatActivity(R.layout.activity_console_log) { private fun Context.isDarkThemeOn(): Boolean { return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == - Configuration.UI_MODE_NIGHT_YES + Configuration.UI_MODE_NIGHT_YES } override fun onCreate(savedInstanceState: Bundle?) { @@ -96,12 +111,28 @@ class ConsoleLogActivity : AppCompatActivity(R.layout.activity_console_log) { super.onCreate(savedInstanceState) initView() setupClickListener() + onBackPressedDispatcher.addCallback( + this, + object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + if (persistentState.consoleLogEnabled) { + showStopDialog() + } else { + finish() + } + return + } + } + ) } private fun initView() { setAdapter() + // update the text view with the time since logs are available io { val sinceTime = consoleLogViewModel.sinceTime() + if (sinceTime == 0L) return@io + val since = Utilities.convertLongToTime(sinceTime, Constants.TIME_FORMAT_3) uiCtx { val desc = getString(R.string.console_log_desc) @@ -110,27 +141,43 @@ class ConsoleLogActivity : AppCompatActivity(R.layout.activity_console_log) { b.consoleLogInfoText.text = descWithTime } } + if (persistentState.consoleLogEnabled) { + b.consoleLogStartStop.text = getString(R.string.hsf_stop_btn_state) + } else { + b.consoleLogStartStop.text = getString(R.string.hsf_start_btn_state) + showStartDialog() // show dialog to user to start logging + } } + var recyclerAdapter: ConsoleLogAdapter? = null + private fun setAdapter() { - var shouldScroll = true b.consoleLogList.setHasFixedSize(true) - layoutManager = CustomLinearLayoutManager(this) + layoutManager = LinearLayoutManager(this@ConsoleLogActivity) b.consoleLogList.layoutManager = layoutManager - val recyclerAdapter = ConsoleLogAdapter(this) - consoleLogViewModel.logs.observe(this) { - recyclerAdapter.submitData(this.lifecycle, it) - if (b.consoleLogList.scrollState == RecyclerView.SCROLL_STATE_IDLE && shouldScroll) { - b.consoleLogList.post { - b.consoleLogList.scrollToPosition(0) // scroll to top if not scrolling + recyclerAdapter = ConsoleLogAdapter(this) + b.consoleLogList.adapter = recyclerAdapter + observeLog() + + /*lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + consoleLogViewModel.logs.collectLatest { pagingData -> + recyclerAdapter.submitData(pagingData) } - } else if (b.consoleLogList.scrollState == RecyclerView.SCROLL_STATE_DRAGGING) { - shouldScroll = false // user is scrolling, don't scroll to top - } else if (b.consoleLogList.scrollState == RecyclerView.SCROLL_STATE_SETTLING) { - shouldScroll = false // user is scrolling, don't scroll to top + } + }*/ + } + + private fun observeLog() { + consoleLogViewModel.logs.observe(this) { l -> + lifecycleScope.launch { + recyclerAdapter?.submitData(l) } } - b.consoleLogList.adapter = recyclerAdapter + } + + private fun unobserveLog1() { + consoleLogViewModel.logs.removeObservers(this) } private fun setupClickListener() { @@ -159,6 +206,134 @@ class ConsoleLogActivity : AppCompatActivity(R.layout.activity_console_log) { } handleSaveOrShareLogs(filePath, SaveType.SHARE) } + b.consoleLogStartStop.setOnClickListener { + persistentState.consoleLogEnabled = !persistentState.consoleLogEnabled + if (persistentState.consoleLogEnabled) { + b.consoleLogStartStop.text = getString(R.string.hsf_stop_btn_state) + } else { + b.consoleLogStartStop.text = getString(R.string.hsf_start_btn_state) + } + } + b.consoleStatInfo.setOnClickListener { showStatsDialog() } + } + + private fun showStartDialog() { + //unobserveLog() + val handler = Handler(Looper.getMainLooper()) + val builder = MaterialAlertDialogBuilder(this) + val title = getString(R.string.console_log_title) + builder.setTitle(title) + builder.setCancelable(true) + builder.setPositiveButton(getString(R.string.hsf_start_btn_state)) { dialogInterface, _ -> + handler.post { + persistentState.consoleLogEnabled = true + b.consoleLogStartStop.text = getString(R.string.hsf_stop_btn_state) + showToastUiCentered( + this, + getString(R.string.config_add_success_toast), + Toast.LENGTH_SHORT + ) + observeLog() + } + dialogInterface.dismiss() + } + + builder.setNeutralButton(getString(R.string.lbl_cancel)) { dialogInterface, _ -> + handler.post { + b.consoleLogStartStop.text = getString(R.string.hsf_start_btn_state) + observeLog() + } + dialogInterface.dismiss() + } + builder.setOnCancelListener { + handler.post { + observeLog() + } + } + val alertDialog: AlertDialog = builder.create() + alertDialog.setCancelable(true) + alertDialog.show() + } + + private fun showStopDialog() { + // unobserveLog() + val handler = Handler(Looper.getMainLooper()) + val builder = MaterialAlertDialogBuilder(this) + val title = getString(R.string.console_log_title) + builder.setTitle(title) + builder.setCancelable(true) + builder.setPositiveButton(getString(R.string.hsf_stop_btn_state)) { dialogInterface, _ -> + handler.post { + persistentState.consoleLogEnabled = false + b.consoleLogStartStop.text = getString(R.string.hsf_start_btn_state) + showToastUiCentered( + this, + getString(R.string.config_add_success_toast), + Toast.LENGTH_SHORT + ) + } + dialogInterface.dismiss() + handler.post { finish() } + } + + builder.setNeutralButton(getString(R.string.lbl_cancel)) { dialogInterface, _ -> + dialogInterface.dismiss() + handler.post { finish() } + } + builder.setOnCancelListener { + observeLog() + } + val alertDialog: AlertDialog = builder.create() + alertDialog.setCancelable(true) + alertDialog.show() + } + + private fun showStatsDialog() { + //unobserveLog() + io { + val stat = VpnController.getNetStat() + val formatedStat = UIUtils.formatNetStat(stat) + uiCtx { + val dialogBinding = DialogInfoRulesLayoutBinding.inflate(layoutInflater) + val builder = MaterialAlertDialogBuilder(this).setView(dialogBinding.root) + val lp = WindowManager.LayoutParams() + val dialog = builder.create() + dialog.show() + lp.copyFrom(dialog.window?.attributes) + lp.width = WindowManager.LayoutParams.MATCH_PARENT + lp.height = WindowManager.LayoutParams.WRAP_CONTENT + + dialog.setCancelable(true) + dialog.window?.attributes = lp + + val heading = dialogBinding.infoRulesDialogRulesTitle + val okBtn = dialogBinding.infoRulesDialogCancelImg + val descText = dialogBinding.infoRulesDialogRulesDesc + dialogBinding.infoRulesDialogRulesIcon.visibility = View.GONE + + heading.text = "Network Stats" + heading.setCompoundDrawablesWithIntrinsicBounds( + ContextCompat.getDrawable(this, R.drawable.ic_info_white), + null, + null, + null + ) + + descText.movementMethod = LinkMovementMethod.getInstance() + descText.text = formatedStat + + okBtn.setOnClickListener { + dialog.dismiss() + observeLog() + } + + dialog.setOnCancelListener { + observeLog() + } + + dialog.show() + } + } } private fun handleSaveOrShareLogs(filePath: String, type: SaveType) { @@ -184,7 +359,7 @@ class ConsoleLogActivity : AppCompatActivity(R.layout.activity_console_log) { workManager.pruneWork() } else if ( WorkInfo.State.CANCELLED == workInfo.state || - WorkInfo.State.FAILED == workInfo.state + WorkInfo.State.FAILED == workInfo.state ) { onFailure() workManager.pruneWork() @@ -199,14 +374,19 @@ class ConsoleLogActivity : AppCompatActivity(R.layout.activity_console_log) { // show success message Logger.i(LOG_TAG_BUG_REPORT, "Logs saved successfully") b.consoleLogProgressBar.visibility = View.GONE - Toast.makeText(this, "Logs saved successfully", Toast.LENGTH_LONG).show() + Toast.makeText(this, getString(R.string.config_add_success_toast), Toast.LENGTH_LONG).show() } private fun onFailure() { // show failure message Logger.i(LOG_TAG_BUG_REPORT, "Logs save failed") b.consoleLogProgressBar.visibility = View.GONE - Toast.makeText(this, "Logs save failed", Toast.LENGTH_LONG).show() + Toast.makeText( + this, + getString(R.string.download_update_dialog_failure_title), + Toast.LENGTH_LONG + ) + .show() } private fun showLogGenerationProgressUi() { @@ -264,7 +444,7 @@ class ConsoleLogActivity : AppCompatActivity(R.layout.activity_console_log) { private fun makeConsoleLogFile(type: SaveType): String? { return try { - val appVersion = getVersionName() + val appVersion = getVersionName() + "_" + System.currentTimeMillis() return if (type.isShare()) { // create file in filesdir, no need to check for permissions val dir = filesDir.canonicalPath + File.separator @@ -366,8 +546,8 @@ class ConsoleLogActivity : AppCompatActivity(R.layout.activity_console_log) { Environment.isExternalStorageManager() } else { // below version 11 - val write = - ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) + val write = 3 + ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) val read = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) read == PackageManager.PERMISSION_GRANTED && write == PackageManager.PERMISSION_GRANTED diff --git a/app/src/full/java/com/celzero/bravedns/viewmodel/ConsoleLogViewModel.kt b/app/src/full/java/com/celzero/bravedns/viewmodel/ConsoleLogViewModel.kt index d8ff386ee..a341c12ae 100644 --- a/app/src/full/java/com/celzero/bravedns/viewmodel/ConsoleLogViewModel.kt +++ b/app/src/full/java/com/celzero/bravedns/viewmodel/ConsoleLogViewModel.kt @@ -23,29 +23,32 @@ import androidx.lifecycle.viewModelScope import androidx.paging.Pager import androidx.paging.PagingConfig import androidx.paging.PagingData +import androidx.paging.PagingSource +import androidx.paging.PagingState import androidx.paging.cachedIn import androidx.paging.liveData import com.celzero.bravedns.database.ConsoleLog import com.celzero.bravedns.database.ConsoleLogDAO import com.celzero.bravedns.util.Constants +import kotlinx.coroutines.flow.Flow -class ConsoleLogViewModel(private val consoleLogDAO: ConsoleLogDAO) : ViewModel() { +class ConsoleLogViewModel(private val dao: ConsoleLogDAO) : ViewModel() { private var filter: MutableLiveData = MutableLiveData() init { - filter.value = "" + filter.postValue("") } val logs = filter.switchMap { input: String -> getLogs(input) } private fun getLogs(filter: String): LiveData> { // filter is unused for now - return Pager(PagingConfig(Constants.LIVEDATA_PAGE_SIZE)) { consoleLogDAO.getLogs() } + return Pager(PagingConfig(Constants.LIVEDATA_PAGE_SIZE)) { dao.getLogs() } .liveData .cachedIn(viewModelScope) } suspend fun sinceTime(): Long { - return consoleLogDAO.sinceTime() + return dao.sinceTime() } } diff --git a/app/src/full/res/layout/activity_console_log.xml b/app/src/full/res/layout/activity_console_log.xml index a66a1b106..6f40d6264 100644 --- a/app/src/full/res/layout/activity_console_log.xml +++ b/app/src/full/res/layout/activity_console_log.xml @@ -18,10 +18,30 @@ android:layout_margin="5dp" android:padding="10dp" android:text="@string/console_log_title" - android:layout_toStartOf="@id/console_log_save" + android:layout_toStartOf="@id/console_stat_info" android:textColor="?attr/primaryTextColor" android:textSize="16sp" /> + + + + + android:src="@drawable/ic_share" /> diff --git a/app/src/main/java/com/celzero/bravedns/database/ConsoleLogDAO.kt b/app/src/main/java/com/celzero/bravedns/database/ConsoleLogDAO.kt index f66aed810..4a35602cd 100644 --- a/app/src/main/java/com/celzero/bravedns/database/ConsoleLogDAO.kt +++ b/app/src/main/java/com/celzero/bravedns/database/ConsoleLogDAO.kt @@ -23,27 +23,31 @@ import androidx.room.OnConflictStrategy import androidx.room.Query import androidx.room.RawQuery import androidx.sqlite.db.SimpleSQLiteQuery +import com.celzero.bravedns.util.Constants @Dao interface ConsoleLogDAO { @Insert suspend fun insert(log: ConsoleLog) - @Query("SELECT * FROM ConsoleLog order by timestamp desc") - suspend fun getAllLogs(): List - @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertBatch(log: List) @RawQuery fun getLogsCursor(query: SimpleSQLiteQuery): Cursor - @Query("select * from ConsoleLog order by id desc") + @Query("select * from ConsoleLog order by id desc LIMIT ${Constants.MAX_LOGS}") fun getLogs(): PagingSource - @Query("DELETE FROM ConsoleLog WHERE id IN (SELECT id FROM ConsoleLog ORDER BY id ASC LIMIT :limit)") - suspend fun deleteOldLogs(limit: Int) + @Query("DELETE FROM ConsoleLog WHERE timestamp < :to") + suspend fun deleteOldLogs(to: Long) - @Query("select timestamp from ConsoleLog order by timestamp desc limit 1") + @Query("select timestamp from ConsoleLog order by id limit 1") suspend fun sinceTime(): Long + + @Query("select count(*) from ConsoleLog") + suspend fun getLogCount(): Int + + @Query("SELECT * FROM ConsoleLog ORDER BY timestamp DESC LIMIT :limit OFFSET :offset") + suspend fun getLogs(offset: Int, limit: Int): List } diff --git a/app/src/main/java/com/celzero/bravedns/database/ConsoleLogRepository.kt b/app/src/main/java/com/celzero/bravedns/database/ConsoleLogRepository.kt index 5036577c2..aac30738e 100644 --- a/app/src/main/java/com/celzero/bravedns/database/ConsoleLogRepository.kt +++ b/app/src/main/java/com/celzero/bravedns/database/ConsoleLogRepository.kt @@ -21,12 +21,15 @@ class ConsoleLogRepository(private val consoleLogDAO: ConsoleLogDAO) { consoleLogDAO.insert(log) } - suspend fun deleteOldLogs(limit: Int) { - consoleLogDAO.deleteOldLogs(limit) + suspend fun deleteOldLogs(to: Long) { + consoleLogDAO.deleteOldLogs(to) } suspend fun insertBatch(logs: List) { consoleLogDAO.insertBatch(logs) } + suspend fun getLogCount(): Int { + return consoleLogDAO.getLogCount() + } } diff --git a/app/src/main/java/com/celzero/bravedns/service/ConsoleLogManager.kt b/app/src/main/java/com/celzero/bravedns/service/ConsoleLogManager.kt index 85f5e11a9..ad6aebc98 100644 --- a/app/src/main/java/com/celzero/bravedns/service/ConsoleLogManager.kt +++ b/app/src/main/java/com/celzero/bravedns/service/ConsoleLogManager.kt @@ -24,13 +24,10 @@ class ConsoleLogManager(private val repository: ConsoleLogRepository) { repository.insert(log) } - private suspend fun deleteOldLogs(limit: Int) { - repository.deleteOldLogs(limit) - } - suspend fun insertBatch(logs: List<*>) { val l = logs as? List ?: return + l.sortedBy { it.id } repository.insertBatch(l) } } diff --git a/app/src/main/res/layout/list_item_console_log.xml b/app/src/main/res/layout/list_item_console_log.xml index 2b6d759db..c447f6ce0 100644 --- a/app/src/main/res/layout/list_item_console_log.xml +++ b/app/src/main/res/layout/list_item_console_log.xml @@ -22,7 +22,7 @@ android:gravity="center" android:fontFamily="serif-monospace" android:layout_centerVertical="true" - android:textColor="?attr/primaryTextColor" + android:textColor="?attr/primaryLightColorText" android:textSize="@dimen/default_font_text_view" /> Date: Mon, 8 Jul 2024 20:42:27 +0530 Subject: [PATCH 040/188] dns-settings-ui: show system DNS IP in DNS settings screen --- .../ui/fragment/DnsSettingsFragment.kt | 41 +++++++++++++++++++ .../res/layout/fragment_dns_configure.xml | 9 ++-- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/DnsSettingsFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/DnsSettingsFragment.kt index 14f4c58b6..d7762e520 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/DnsSettingsFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/DnsSettingsFragment.kt @@ -16,12 +16,15 @@ package com.celzero.bravedns.ui.fragment import Logger +import android.content.DialogInterface import android.content.Intent import android.os.Bundle +import android.view.MotionEvent import android.view.View import android.view.animation.Animation import android.view.animation.RotateAnimation import android.widget.CompoundButton +import android.widget.RadioButton import android.widget.Toast import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope @@ -40,11 +43,14 @@ import com.celzero.bravedns.ui.activity.ConfigureRethinkBasicActivity import com.celzero.bravedns.ui.activity.DnsListActivity import com.celzero.bravedns.ui.activity.PauseActivity import com.celzero.bravedns.ui.bottomsheet.LocalBlocklistsBottomSheet +import com.celzero.bravedns.util.UIUtils import com.celzero.bravedns.util.UIUtils.fetchColor import com.celzero.bravedns.util.Utilities import com.celzero.bravedns.util.Utilities.isPlayStoreFlavour +import com.google.android.material.dialog.MaterialAlertDialogBuilder import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import org.koin.android.ext.android.get import org.koin.android.ext.android.inject import java.util.concurrent.TimeUnit @@ -331,6 +337,13 @@ class DnsSettingsFragment : } b.networkDnsRb.setOnClickListener { + if (isSystemDns()) { + io { + val sysDns = VpnController.getSystemDns() + uiCtx { showSystemDnsDialog(sysDns) } + } + return@setOnClickListener + } // network dns proxy setNetworkDns() } @@ -376,6 +389,30 @@ class DnsSettingsFragment : } } + private fun showSystemDnsDialog(dns: String) { + val builder = MaterialAlertDialogBuilder(requireContext()) + .setTitle(R.string.network_dns) + .setMessage(dns) + .setCancelable(true) + .setPositiveButton(R.string.ada_noapp_dialog_positive) { di, _ -> + di.dismiss() + } + .setNeutralButton(requireContext().getString(R.string.dns_info_neutral)) { _: DialogInterface, _: Int -> + UIUtils.clipboardCopy( + requireContext(), + dns, + requireContext().getString(R.string.copy_clipboard_label) + ) + Utilities.showToastUiCentered( + requireContext(), + requireContext().getString(R.string.info_dialog_url_copy_toast_msg), + Toast.LENGTH_SHORT + ) + } + val dialog = builder.create() + dialog.show() + } + private fun initAnimation() { animation = RotateAnimation( @@ -428,6 +465,10 @@ class DnsSettingsFragment : lifecycleScope.launch(Dispatchers.IO) { f() } } + private suspend fun uiCtx(f: suspend () -> Unit) { + withContext(Dispatchers.Main) { f() } + } + override fun onBtmSheetDismiss() { if (!isAdded) return diff --git a/app/src/main/res/layout/fragment_dns_configure.xml b/app/src/main/res/layout/fragment_dns_configure.xml index e1d20dff4..36a726cc8 100644 --- a/app/src/main/res/layout/fragment_dns_configure.xml +++ b/app/src/main/res/layout/fragment_dns_configure.xml @@ -72,7 +72,7 @@ android:layout_marginTop="5dp" android:paddingTop="5dp"> - - - - Date: Mon, 8 Jul 2024 20:43:37 +0530 Subject: [PATCH 041/188] daemon: implement a single-thread factory adopted from java.util.concurrent.Executors.DefaultThreadFactory --- .../java/com/celzero/bravedns/util/Daemons.kt | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 app/src/main/java/com/celzero/bravedns/util/Daemons.kt diff --git a/app/src/main/java/com/celzero/bravedns/util/Daemons.kt b/app/src/main/java/com/celzero/bravedns/util/Daemons.kt new file mode 100644 index 000000000..977aa07b0 --- /dev/null +++ b/app/src/main/java/com/celzero/bravedns/util/Daemons.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2024 RethinkDNS and its authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.celzero.bravedns.util + +import kotlinx.coroutines.asCoroutineDispatcher +import java.util.concurrent.Executors +import java.util.concurrent.ThreadFactory +import java.util.concurrent.atomic.AtomicInteger + +object Daemons { + + fun make(tag: String) = Executors.newSingleThreadExecutor(Factory(tag)).asCoroutineDispatcher() + +} + +// adopted from: java.util.concurrent.Executors.DefaultThreadFactory +class Factory(tag: String = "d"): ThreadFactory { + private val group: ThreadGroup? + private val threadNumber = AtomicInteger(1) + private val namePrefix: String + + init { + val s = System.getSecurityManager() + group = if ((s != null)) s.threadGroup else Thread.currentThread().threadGroup + namePrefix = tag + poolNumber.getAndIncrement() + "t" + } + + override fun newThread(r: Runnable): Thread { + val t = Thread( + group, r, + namePrefix + threadNumber.getAndIncrement(), + 0 + ) + if (t.isDaemon) t.isDaemon = false + if (t.priority != Thread.NORM_PRIORITY) t.priority = Thread.NORM_PRIORITY + return t + } + + companion object { + private val poolNumber = AtomicInteger(1) + } +} From 1083765cc2a7c2bc36062b6b08eb7546e37c8b41 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 8 Jul 2024 20:44:44 +0530 Subject: [PATCH 042/188] network-ui: streamline edit custom option in UI --- .../res/layout/activity_tunnel_settings.xml | 66 ++++--------------- 1 file changed, 12 insertions(+), 54 deletions(-) diff --git a/app/src/full/res/layout/activity_tunnel_settings.xml b/app/src/full/res/layout/activity_tunnel_settings.xml index 965eac5c6..f3721f055 100644 --- a/app/src/full/res/layout/activity_tunnel_settings.xml +++ b/app/src/full/res/layout/activity_tunnel_settings.xml @@ -543,6 +543,7 @@ android:src="@drawable/ic_connectivity_checks" /> - - - - - - - - - - - - - - + android:text="@string/rt_edit_dialog_positive" + android:background="@drawable/home_screen_button_stop_bg" + android:layout_gravity="end" + android:layout_marginEnd="10dp" + android:layout_marginBottom="15dp" + android:padding="5dp" + android:textColor="?attr/primaryTextColor" + /> From 0ade1c79d4b61f639a698890071c15713ea7de27 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 8 Jul 2024 20:45:33 +0530 Subject: [PATCH 043/188] reset the Rethink app as allowed by default --- .../celzero/bravedns/ui/HomeScreenActivity.kt | 45 +++++++++---------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/HomeScreenActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/HomeScreenActivity.kt index aa2d79071..d3ca56736 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/HomeScreenActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/HomeScreenActivity.kt @@ -31,11 +31,13 @@ import android.net.Uri import android.os.Bundle import android.os.PersistableBundle import android.os.SystemClock +import android.view.View import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.biometric.BiometricManager import androidx.biometric.BiometricPrompt import androidx.core.content.ContextCompat +import androidx.core.content.res.ResourcesCompat import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.setupWithNavController @@ -55,6 +57,7 @@ import com.celzero.bravedns.backup.BackupHelper.Companion.INTENT_RESTART_APP import com.celzero.bravedns.backup.BackupHelper.Companion.INTENT_SCHEME import com.celzero.bravedns.backup.RestoreAgent import com.celzero.bravedns.data.AppConfig +import com.celzero.bravedns.database.AppInfoRepository import com.celzero.bravedns.database.RefreshDatabase import com.celzero.bravedns.databinding.ActivityHomeScreenBinding import com.celzero.bravedns.service.AppUpdater @@ -88,7 +91,7 @@ class HomeScreenActivity : AppCompatActivity(R.layout.activity_home_screen) { private val b by viewBinding(ActivityHomeScreenBinding::bind) private val persistentState by inject() - private val appConfig by inject() + private val appInfoDb by inject() private val appUpdateManager by inject() private val rdb by inject() @@ -122,7 +125,6 @@ class HomeScreenActivity : AppCompatActivity(R.layout.activity_home_screen) { // do not launch on board activity when app is running on TV if (persistentState.firstTimeLaunch && !isAppRunningOnTv()) { launchOnboardActivity() - rdnsRemote() return } updateNewVersion() @@ -393,18 +395,8 @@ class HomeScreenActivity : AppCompatActivity(R.layout.activity_home_screen) { // reset the bio metric auth time, as now the value is changed from System.currentTimeMillis // to SystemClock.elapsedRealtime persistentState.biometricAuthTime = SystemClock.elapsedRealtime() - } - - private fun rdnsRemote() { - // enforce the dns to sky for play store build, and max for website and f-droid build - // on first time launch - io { - if (isPlayStoreFlavour()) { - appConfig.switchRethinkDnsToSky() - } else { - appConfig.switchRethinkDnsToMax() - } - } + // set the rethink app in firewall mode as allowed by default + appInfoDb.resetRethinkAppFirewallMode() } // fixme: find a cleaner way to implement this, move this to some other place @@ -603,17 +595,22 @@ class HomeScreenActivity : AppCompatActivity(R.layout.activity_home_screen) { } private fun showUpdateCompleteSnackbar() { - val snack = - Snackbar.make( - b.container, - getString(R.string.update_complete_snack_message), - Snackbar.LENGTH_INDEFINITE - ) - snack.setAction(getString(R.string.update_complete_action_snack)) { - appUpdateManager.completeUpdate() + try { + val container: View = findViewById(R.id.container) + val snack = + Snackbar.make( + container, + getString(R.string.update_complete_snack_message), + Snackbar.LENGTH_INDEFINITE + ) + snack.setAction(getString(R.string.update_complete_action_snack)) { + appUpdateManager.completeUpdate() + } + snack.setActionTextColor(ContextCompat.getColor(this, R.color.primaryLightColorText)) + snack.show() + } catch (e: Exception) { + Logger.e(LOG_TAG_UI, "Error showing update complete snackbar: ${e.message}", e) } - snack.setActionTextColor(ContextCompat.getColor(this, R.color.primaryLightColorText)) - snack.show() } private fun showDownloadDialog( From 80ea6c1472e224af65e4bf8413dbf1a32e87391b Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 8 Jul 2024 20:47:22 +0530 Subject: [PATCH 044/188] consolidate data usage for rethink app as well --- .../celzero/bravedns/database/AppInfoDAO.kt | 6 ++++ .../bravedns/database/AppInfoRepository.kt | 8 +++++ .../bravedns/scheduler/DataUsageUpdater.kt | 32 ++++++++++++++++++- 3 files changed, 45 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/celzero/bravedns/database/AppInfoDAO.kt b/app/src/main/java/com/celzero/bravedns/database/AppInfoDAO.kt index 4edab12e3..fba6d1f06 100644 --- a/app/src/main/java/com/celzero/bravedns/database/AppInfoDAO.kt +++ b/app/src/main/java/com/celzero/bravedns/database/AppInfoDAO.kt @@ -152,4 +152,10 @@ interface AppInfoDAO { @Query("update AppInfo set isProxyExcluded = :isProxyExcluded where uid = :uid") fun updateProxyExcluded(uid: Int, isProxyExcluded: Boolean) + + @Query("update AppInfo set firewallStatus = 5, connectionStatus = 3 where packageName = 'com.celzero.bravedns'") + fun resetRethinkAppFirewallMode() + + @Query("select uid from AppInfo where packageName = :packageName") + fun getAppInfoUidForPackageName(packageName: String): Int } diff --git a/app/src/main/java/com/celzero/bravedns/database/AppInfoRepository.kt b/app/src/main/java/com/celzero/bravedns/database/AppInfoRepository.kt index 6f4e88662..437725fc0 100644 --- a/app/src/main/java/com/celzero/bravedns/database/AppInfoRepository.kt +++ b/app/src/main/java/com/celzero/bravedns/database/AppInfoRepository.kt @@ -97,4 +97,12 @@ class AppInfoRepository(private val appInfoDAO: AppInfoDAO) { fun updateProxyExcluded(uid: Int, isProxyExcluded: Boolean) { appInfoDAO.updateProxyExcluded(uid, isProxyExcluded) } + + fun resetRethinkAppFirewallMode() { + appInfoDAO.resetRethinkAppFirewallMode() + } + + fun getAppInfoUidForPackageName(packageName: String): Int { + return appInfoDAO.getAppInfoUidForPackageName(packageName) + } } diff --git a/app/src/main/java/com/celzero/bravedns/scheduler/DataUsageUpdater.kt b/app/src/main/java/com/celzero/bravedns/scheduler/DataUsageUpdater.kt index be566a5c0..1f023847c 100644 --- a/app/src/main/java/com/celzero/bravedns/scheduler/DataUsageUpdater.kt +++ b/app/src/main/java/com/celzero/bravedns/scheduler/DataUsageUpdater.kt @@ -22,6 +22,7 @@ import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import com.celzero.bravedns.database.AppInfoRepository import com.celzero.bravedns.database.ConnectionTrackerRepository +import com.celzero.bravedns.database.RethinkLogRepository import com.celzero.bravedns.service.PersistentState import com.celzero.bravedns.util.Constants import org.koin.core.component.KoinComponent @@ -30,6 +31,7 @@ import org.koin.core.component.inject class DataUsageUpdater(context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams), KoinComponent { private val connTrackRepository by inject() + private val rethinkDb by inject() private val appInfoRepository by inject() private val persistentState by inject() @@ -54,10 +56,38 @@ class DataUsageUpdater(context: Context, workerParams: WorkerParameters) : Logger.d(LOG_TAG_SCHEDULER, "Data usage for ${it.uid}, $upload, $download") appInfoRepository.updateDataUsageByUid(it.uid, upload, download) } catch (e: Exception) { - Logger.e(LOG_TAG_SCHEDULER, "Exception in data usage updater: ${e.message}", e) + Logger.e(LOG_TAG_SCHEDULER, "err in data usage updater: ${e.message}", e) } } + + updateRethinkDataUsage(previousTimestamp, currentTimestamp) + persistentState.prevDataUsageCheck = currentTimestamp Logger.i(LOG_TAG_SCHEDULER, "Data usage updated for all apps at $currentTimestamp") } + + private suspend fun updateRethinkDataUsage(prev: Long, curr: Long) { + try { + // get rethink's uid from the database + val uid = + appInfoRepository.getAppInfoUidForPackageName(Constants.RETHINK_PACKAGE) + + val prevDataUsage = rethinkDb.getDataUsage(prev, curr) + val currDataUsage = appInfoRepository.getDataUsageByUid(uid) + + if (currDataUsage.uploadBytes == 0L && currDataUsage.downloadBytes == 0L) { + // if the data usage is 0, then no need to update the database + Logger.d(LOG_TAG_SCHEDULER, "rinr, data usage is 0 for $uid") + return + } + + val upload = currDataUsage.uploadBytes + prevDataUsage.uploadBytes + val download = currDataUsage.downloadBytes + prevDataUsage.downloadBytes + + Logger.d(LOG_TAG_SCHEDULER, "rinr, data usage:($uid), $upload, $download") + appInfoRepository.updateDataUsageByUid(uid, upload, download) + } catch (e: Exception) { + Logger.e(LOG_TAG_SCHEDULER, "err in rinr data usage updater: ${e.message}", e) + } + } } From ffd3cd4055f03a6e3339785353e9aa452f1936b5 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 8 Jul 2024 20:49:36 +0530 Subject: [PATCH 045/188] app-info-ui: display package names for all apps --- app/src/full/res/layout/activity_app_details.xml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/full/res/layout/activity_app_details.xml b/app/src/full/res/layout/activity_app_details.xml index d0ef2fb80..897472691 100644 --- a/app/src/full/res/layout/activity_app_details.xml +++ b/app/src/full/res/layout/activity_app_details.xml @@ -46,6 +46,14 @@ android:textColor="?primaryTextColor" android:textSize="@dimen/extra_large_font_text_view" /> + + + android:paddingBottom="15dp"> Date: Thu, 11 Jul 2024 19:16:22 +0530 Subject: [PATCH 046/188] ui: show console log icon in network logs screen --- .../main/res/layout/activity_network_logs.xml | 27 ++++++++++++++++--- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/layout/activity_network_logs.xml b/app/src/main/res/layout/activity_network_logs.xml index 5780d8e7b..ce0041b6d 100644 --- a/app/src/main/res/layout/activity_network_logs.xml +++ b/app/src/main/res/layout/activity_network_logs.xml @@ -8,11 +8,30 @@ android:orientation="vertical" tools:context=".ui.activity.FirewallActivity"> - + android:layout_height="wrap_content"> + + + + + + Date: Thu, 11 Jul 2024 19:16:55 +0530 Subject: [PATCH 047/188] ui: handle toast msg context errors --- .../com/celzero/bravedns/util/Utilities.kt | 30 ++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/celzero/bravedns/util/Utilities.kt b/app/src/main/java/com/celzero/bravedns/util/Utilities.kt index c3ac8226a..a92f588cf 100644 --- a/app/src/main/java/com/celzero/bravedns/util/Utilities.kt +++ b/app/src/main/java/com/celzero/bravedns/util/Utilities.kt @@ -45,6 +45,7 @@ import androidx.core.content.getSystemService import androidx.lifecycle.LifecycleCoroutineScope import com.celzero.bravedns.BuildConfig import com.celzero.bravedns.R +import com.celzero.bravedns.RethinkDnsApplication.Companion.DEBUG import com.celzero.bravedns.database.AppInfoRepository.Companion.NO_PACKAGE import com.celzero.bravedns.net.doh.CountryMap import com.celzero.bravedns.service.BraveVPNService @@ -288,7 +289,34 @@ object Utilities { fun showToastUiCentered(context: Context, message: String, toastLength: Int) { try { - Toast.makeText(context, message, toastLength).show() + // check if the context is ui context or not + if (context is androidx.appcompat.app.AppCompatActivity) { + context.runOnUiThread { + Toast.makeText(context, message, toastLength).show() + } + return + } else if (context is androidx.fragment.app.FragmentActivity) { + context.runOnUiThread { + Toast.makeText(context, message, toastLength).show() + } + return + } else if (context is android.app.Activity) { + context.runOnUiThread { + Toast.makeText(context, message, toastLength).show() + } + return + } else if (context is android.app.Application) { + Logger.w(LOG_TAG_VPN, "toast err: context not found") + if (DEBUG) { // for testing purpose + Toast.makeText(context, message, toastLength).show() + } + } else { + Logger.w(LOG_TAG_VPN, "toast err: context not found") + if (DEBUG) { // for testing purpose + Toast.makeText(context, message, toastLength).show() + } + } + } catch (e: IllegalStateException) { Logger.w(LOG_TAG_VPN, "toast err: ${e.message}") } catch (e: IllegalAccessException) { From 285a67707daebf6dced2f4a1e4f8075b8c71eded Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 11 Jul 2024 19:17:46 +0530 Subject: [PATCH 048/188] batcher: handle close with a atomic boolean --- .../celzero/bravedns/util/NetLogBatcher.kt | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/celzero/bravedns/util/NetLogBatcher.kt b/app/src/main/java/com/celzero/bravedns/util/NetLogBatcher.kt index ccd50883b..992fd9c06 100644 --- a/app/src/main/java/com/celzero/bravedns/util/NetLogBatcher.kt +++ b/app/src/main/java/com/celzero/bravedns/util/NetLogBatcher.kt @@ -18,6 +18,8 @@ package com.celzero.bravedns.util import Logger import Logger.LOG_BATCH_LOGGER +import android.util.Log +import co.touchlab.stately.concurrency.AtomicBoolean import kotlinx.coroutines.CloseableCoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineName @@ -43,13 +45,17 @@ class NetLogBatcher( private val processor: suspend (List) -> Unit, private val updator: suspend (List) -> Unit = { _ -> } ) { + companion object { + private const val DEBUG = true + } + // i keeps track of currently in-use buffer var lsn = 0 private val nprod = CoroutineName(tag + "Producer") // batches writes private val nsig = CoroutineName(tag + "Signal") private val ncons = CoroutineName(tag + "Consumer") // writes batches to db - + private val closed = AtomicBoolean(false) // dispatch buffer to consumer if greater than batch size private val batchSize = 20 @@ -77,23 +83,16 @@ class NetLogBatcher( scope.async { sig() } scope.async { consumeAdd() } scope.async { consumeUpdate() } - - // monitor for cancellation on the default dispatcher - scope.launch { monitorCancellation() } } // stackoverflow.com/a/68905423 - private suspend fun monitorCancellation() { - try { - awaitCancellation() - } finally { - withContext(NonCancellable) { - signal.close() - buffersCh.close() - updatesCh.close() - Logger.i(LOG_BATCH_LOGGER, "end") - } - } + suspend fun close() = withContext(NonCancellable) { + if (closed.compareAndSet(false, true)) { + signal.close() + buffersCh.close() + updatesCh.close() + logd("$tag end") + } } private suspend fun consumeAdd() = @@ -110,6 +109,11 @@ class NetLogBatcher( } } + private fun logd(msg: String) { + // write batcher logs only in DEBUG mode to avoid log spam + if (DEBUG) Log.d(LOG_BATCH_LOGGER, msg) + } + private suspend fun txswap() { val b = batches batches = mutableListOf() // swap buffers @@ -119,7 +123,7 @@ class NetLogBatcher( updates = mutableListOf() // swap buffers updatesCh.send(u) - Logger.d(LOG_BATCH_LOGGER, "txswap (${lsn}) b: ${b.size}, u: ${u.size}") + logd( "txswap (${lsn}) b: ${b.size}, u: ${u.size}") lsn = (lsn + 1) } @@ -150,25 +154,22 @@ class NetLogBatcher( // consume all signals for (tracklsn in signal) { if (tracklsn < lsn) { - Logger.d(LOG_BATCH_LOGGER, "dup signal skip $tracklsn") + logd("dup signal skip $tracklsn") continue } // do not honor the signal for 'l' if a[l] is empty // this can happen if the signal for 'l' is processed // after the fact that 'l' has been swapped out by 'batch' if (batches.size <= 0 && updates.size <= 0) { - Logger.d(LOG_BATCH_LOGGER, "signal continue") + logd("signal continue") continue } else { - Logger.d(LOG_BATCH_LOGGER, "signal sleep $waitms ms") + logd("signal sleep $waitms ms") } // wait for 'batch' to dispatch delay(waitms) - Logger.d( - LOG_BATCH_LOGGER, - "signal wait over, sz(b: ${batches.size}, u: ${updates.size}) / cur-buf(${lsn})" - ) + logd("signal wait over, sz(b: ${batches.size}, u: ${updates.size}) / cur-buf(${lsn})") // 'l' is the current buffer, that is, 'l == i', // and 'batch' hasn't dispatched it, From 779311cb374ffea228be0712a5474f9fab7e4984 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 11 Jul 2024 19:18:36 +0530 Subject: [PATCH 049/188] batcher: create batchers for every new scope --- .../celzero/bravedns/service/NetLogTracker.kt | 111 ++++++++++++------ 1 file changed, 75 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/com/celzero/bravedns/service/NetLogTracker.kt b/app/src/main/java/com/celzero/bravedns/service/NetLogTracker.kt index 3cfb5219e..8a2c6dc90 100644 --- a/app/src/main/java/com/celzero/bravedns/service/NetLogTracker.kt +++ b/app/src/main/java/com/celzero/bravedns/service/NetLogTracker.kt @@ -16,7 +16,9 @@ package com.celzero.bravedns.service +import Logger.LOG_BATCH_LOGGER import android.content.Context +import android.util.Log import backend.DNSSummary import com.celzero.bravedns.RethinkDnsApplication.Companion.DEBUG import com.celzero.bravedns.data.ConnTrackerMetaData @@ -29,22 +31,19 @@ import com.celzero.bravedns.database.DnsLog import com.celzero.bravedns.database.DnsLogRepository import com.celzero.bravedns.database.RethinkLog import com.celzero.bravedns.database.RethinkLogRepository +import com.celzero.bravedns.util.Daemons import com.celzero.bravedns.util.NetLogBatcher +import java.util.Calendar import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.ExperimentalCoroutinesApi -import kotlinx.coroutines.Job import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.delay import kotlinx.coroutines.launch -import kotlinx.coroutines.newSingleThreadContext import kotlinx.coroutines.withContext import org.koin.core.component.KoinComponent import org.koin.core.component.inject -import java.util.Calendar class NetLogTracker internal constructor( @@ -58,7 +57,7 @@ internal constructor( private val dnsLatencyTracker by inject() - private var scope: CoroutineScope? = null + @Volatile private var scope: CoroutineScope? = null private var dnsdb: DnsLogTracker = DnsLogTracker(dnsLogRepository, persistentState, context) private var ipdb: IPTracker = @@ -67,44 +66,79 @@ internal constructor( private var dnsBatcher: NetLogBatcher? = null private var ipBatcher: NetLogBatcher? = null - private var rrBatcher :NetLogBatcher? = null + private var rrBatcher: NetLogBatcher? = null private var consoleLogBatcher: NetLogBatcher? = null // a single thread to run sig and batch co-routines in; // to avoid use of mutex/semaphores over shared-state // looper is never closed / cancelled and is always active - @OptIn(DelicateCoroutinesApi::class, ExperimentalCoroutinesApi::class) - private val looper = newSingleThreadContext("nlbLooper") + private val looper = Daemons.make("netlog") companion object { private const val UPDATE_DELAY = 2500L } - @OptIn(ExperimentalCoroutinesApi::class) suspend fun restart(s: CoroutineScope) { this.scope = s + serializer("restart") { + + // create new batchers on every new scope as their lifecycle is tied to the scope + val b1 = NetLogBatcher("dns", looper, dnsdb::insertBatch) + val b2 = + NetLogBatcher( + "ip", + looper, + ipdb::insertBatch, + ipdb::updateBatch + ) + val b3 = + NetLogBatcher( + "rr", + looper, + ipdb::insertRethinkBatch, + ipdb::updateRethinkBatch + ) + val b4 = + NetLogBatcher("console", looper, consoleLogDb::insertBatch) + + b1.begin(s) + b2.begin(s) + b3.begin(s) + b4.begin(s) + + this.dnsBatcher = b1 + this.ipBatcher = b2 + this.rrBatcher = b3 + this.consoleLogBatcher = b4 + + s.launch(Dispatchers.IO) { monitorCancellation() } + Log.d(LOG_BATCH_LOGGER, "tracker: restart, $scope") + } + } - // create new batchers on every new scope as their lifecycle is tied to the scope - val b1 = NetLogBatcher("dns", looper, dnsdb::insertBatch) - val b2 = NetLogBatcher("ip", looper, ipdb::insertBatch, ipdb::updateBatch) - val b3 = NetLogBatcher("rr", looper, ipdb::insertRethinkBatch, ipdb::updateRethinkBatch) - val b4 = NetLogBatcher("lc", looper, consoleLogDb::insertBatch) - - b1.begin(s) - b2.begin(s) - b3.begin(s) - b4.begin(s) - - this.dnsBatcher = b1 - this.ipBatcher = b2 - this.rrBatcher = b3 - this.consoleLogBatcher = b4 + // stackoverflow.com/a/68905423 + private suspend fun monitorCancellation() { + try { + awaitCancellation() + } finally { + withContext(looper + NonCancellable) { + dnsBatcher?.close() + ipBatcher?.close() + rrBatcher?.close() + consoleLogBatcher?.close() + dnsBatcher = null + ipBatcher = null + rrBatcher = null + consoleLogBatcher = null + Logger.d(LOG_BATCH_LOGGER, "tracker: close scope") + } + } } fun writeIpLog(info: ConnTrackerMetaData) { if (!persistentState.logsEnabled) return - io("writeIpLog") { + serializer("writeIpLog") { val connTracker = ipdb.makeConnectionTracker(info) ipBatcher?.add(connTracker) } @@ -113,8 +147,8 @@ internal constructor( fun writeRethinkLog(info: ConnTrackerMetaData) { if (!persistentState.logsEnabled) return - io("writeRethinkLog") { - val rlog = ipdb.makeRethinkLogs(info) ?: return@io + serializer("writeRethinkLog") { + val rlog = ipdb.makeRethinkLogs(info) ?: return@serializer rrBatcher?.add(rlog) } } @@ -122,8 +156,9 @@ internal constructor( fun updateIpSummary(summary: ConnectionSummary) { if (!persistentState.logsEnabled) return val d = Logger.LoggerType.fromId(persistentState.goLoggerLevel.toInt()) - val debug = d.isLessThan(Logger.LoggerType.DEBUG) - io("updateIpSmm") { + val debug = d.isLessThan(Logger.LoggerType.INFO) // debug, verbose, very verbose + + serializer("updateIpSmm") { val s = if (debug && summary.targetIp?.isNotEmpty() == true) { ipdb.makeSummaryWithTarget(summary) @@ -141,7 +176,7 @@ internal constructor( fun updateRethinkSummary(summary: ConnectionSummary) { if (!persistentState.logsEnabled) return - io("updateRethinkSmm") { + serializer("updateRethinkSmm") { val s = if (DEBUG && summary.targetIp?.isNotEmpty() == true) { ipdb.makeSummaryWithTarget(summary) @@ -162,8 +197,8 @@ internal constructor( val transaction = dnsdb.processOnResponse(summary) transaction.responseCalendar = Calendar.getInstance() - // refresh latency from GoVpnAdapter - io("refreshDnsLatency") { dnsLatencyTracker.refreshLatencyIfNeeded(transaction) } + // TODO: move this to generic Dispatcher.IO; serializer is not required + serializer("refreshDnsLatency") { dnsLatencyTracker.refreshLatencyIfNeeded(transaction) } // TODO: This method should be part of BraveVPNService dnsdb.updateVpnConnectionState(transaction) @@ -171,16 +206,20 @@ internal constructor( if (!persistentState.logsEnabled) return val dnsLog = dnsdb.makeDnsLogObj(transaction) - io("writeDnsLog") { dnsBatcher?.add(dnsLog) } + serializer("writeDnsLog") { dnsBatcher?.add(dnsLog) } } - fun writeConsoleLog(msg: String) { + fun writeConsoleLog(log: ConsoleLog) { io("writeConsoleLog") { - val log = ConsoleLog(0, msg, System.currentTimeMillis()) consoleLogBatcher?.add(log) } } + private fun serializer(s: String, f: suspend () -> Unit) = + scope?.launch(CoroutineName(s) + looper) { f() } + ?: Log.e(LOG_BATCH_LOGGER, "scope is null", Exception()) + private fun io(s: String, f: suspend () -> Unit) = scope?.launch(CoroutineName(s) + Dispatchers.IO) { f() } + ?: Log.e(LOG_BATCH_LOGGER, "scope is null", Exception()) } From 8246d7c94dfd7259a8bfca389388e96fc9715ffd Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 11 Jul 2024 19:20:28 +0530 Subject: [PATCH 050/188] wg: check for both valid config and supported ip version --- .../bravedns/service/WireguardManager.kt | 58 ++++++++++++++++--- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt b/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt index bb4ace2eb..4d0e0e680 100644 --- a/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt +++ b/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt @@ -31,6 +31,7 @@ import com.celzero.bravedns.wireguard.BadConfigException import com.celzero.bravedns.wireguard.Config import com.celzero.bravedns.wireguard.Peer import com.celzero.bravedns.wireguard.WgInterface +import inet.ipaddr.IPAddressString import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow @@ -441,7 +442,24 @@ object WireguardManager : KoinComponent { // if the app is added to config, return the config if it is active or lockdown if (config != null && (config.isActive || config.isLockdown)) { - return config + val isValidConfig = if (config.isLockdown) { + // if lockdown is enabled, canRoute checks peer configuration and if it returns + // "false", then the connection will be sent to base and not dropped + // if lockdown is disabled, then canRoute returns default (true) which + // will have the effect of blocking all connections + // ie, if lockdown is enabled, split-tunneling happens as expected but if + // lockdown is disabled, it has the effect of blocking all connections + isValidWgConnForIp(id, ip, true) + } else { + isValidWgConnForIp(id, ip, false) + } + if (isValidConfig) { + Logger.d(LOG_TAG_PROXY, "app config mapping found for uid: $uid, $configId") + return config + } else { + Logger.d(LOG_TAG_PROXY, "app config mapping found for uid: $uid, $configId, but not valid") + // pass-through and check if any catch-all config is enabled + } } // check if any catch-all config is enabled if (configId == "" || !configId.contains(ProxyManager.ID_WG_BASE) || config == null) { @@ -449,12 +467,12 @@ object WireguardManager : KoinComponent { // there maybe catch-all config enabled, so return the active catch-all config val catchAllConfig = mappings.find { it.isActive && it.isCatchAll } return if (catchAllConfig == null) { - Logger.i(LOG_TAG_PROXY, "catch all config not found for uid: $uid") + Logger.d(LOG_TAG_PROXY, "catch all config not found for uid: $uid") null } else { val optimalId = fetchOptimalCatchAllConfig(uid, ip) if (optimalId == null) { - Logger.i(LOG_TAG_PROXY, "no catch all config found for uid: $uid") + Logger.d(LOG_TAG_PROXY, "no catch all config found for uid: $uid") null } else { Logger.i(LOG_TAG_PROXY, "catch all config found for uid: $uid, $optimalId") @@ -463,7 +481,12 @@ object WireguardManager : KoinComponent { } } - // if the app is not added to any config, and no catch-all config is enabled + // no catch-all wg matches the uid & ip combination, return config (assigned config) + if (config.isActive || config.isLockdown) { + Logger.d(LOG_TAG_PROXY, "app config mapping found for uid: $uid, $configId") + return config + } + return null } @@ -500,7 +523,7 @@ object WireguardManager : KoinComponent { if (available) { val wgId = catchAllAppConfigCache.value[uid] if (wgId != null) { - if (isProxyConnectionValid(wgId, ip)) { + if (isValidWgConnForIp(wgId, ip)) { Logger.d(LOG_TAG_PROXY, "optimalCatchAllConfig: returning cached wgId: $wgId") return wgId // return the already mapped wgId which is active } @@ -511,7 +534,7 @@ object WireguardManager : KoinComponent { Logger.d(LOG_TAG_PROXY, "optimalCatchAllConfig: fetching new wgId for uid: $uid") val catchAllList = mappings.filter { it.isActive && it.isCatchAll } catchAllList.forEach { - if (isProxyConnectionValid(it.id, ip)) { + if (isValidWgConnForIp(it.id, ip)) { // note the uid and wgid in a cache, so that we can use it for further requests catchAllAppConfigCache.value += uid to it.id Logger.d(LOG_TAG_PROXY, "optimalCatchAllConfig: returning new wgId: ${it.id}") @@ -524,6 +547,13 @@ object WireguardManager : KoinComponent { return catchAllList.random().id } + private suspend fun isValidWgConnForIp(wgId: Int, ip: String, default: Boolean = false): Boolean { + val isSupported = isSupportedIpVersion(wgId, ip) + val isValid = isProxyConnectionValid(wgId, ip, default) + Logger.d(LOG_TAG_PROXY, "isValidWgConnForIp: $wgId? isSupported: $isSupported, isValid: $isValid") + return isSupported && isValid + } + private fun pingCatchAllConfigs(catchAllConfigs: List) { io { // ping the catch-all config @@ -534,7 +564,7 @@ object WireguardManager : KoinComponent { } } - private suspend fun isProxyConnectionValid(wgId: Int, ip: String, default: Boolean = false): Boolean { + private suspend fun isProxyConnectionValid(wgId: Int, ip: String, default: Boolean): Boolean { // check if the handshake is less than 3 minutes (VALID_LAST_OK_SEC) // and if the ip can be routed val id = ProxyManager.ID_WG_BASE + wgId @@ -543,6 +573,20 @@ object WireguardManager : KoinComponent { return isValidLastOk(wgId) && canRoute } + private suspend fun isSupportedIpVersion(wgId: Int, ip: String): Boolean { + val ipVersion = IPAddressString(ip).toAddress().ipVersion + val id = ProxyManager.ID_WG_BASE + wgId + val supportedVersion = VpnController.getSupportedIpVersion(id) + Logger.d(LOG_TAG_PROXY, "isSupportedIpVersion: $wgId? ipVersion: $ipVersion, supportedVersion: $supportedVersion") + if (ipVersion.isIPv4 && supportedVersion.first) { + return true + } + if (ipVersion.isIPv6 && supportedVersion.second) { + return true + } + return false + } + private suspend fun isValidLastOk(wgId: Int): Boolean { val id = ProxyManager.ID_WG_BASE + wgId val stat = VpnController.getProxyStats(id) ?: return false From 9c542063b926901d8b66b6c4fe4eafe305c067eb Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 11 Jul 2024 19:20:54 +0530 Subject: [PATCH 051/188] scheduler: change logs to info level --- .../com/celzero/bravedns/scheduler/WorkScheduler.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/scheduler/WorkScheduler.kt b/app/src/full/java/com/celzero/bravedns/scheduler/WorkScheduler.kt index d529b4fa6..51a9bed09 100644 --- a/app/src/full/java/com/celzero/bravedns/scheduler/WorkScheduler.kt +++ b/app/src/full/java/com/celzero/bravedns/scheduler/WorkScheduler.kt @@ -46,14 +46,14 @@ class WorkScheduler(val context: Context) { const val APP_EXIT_INFO_JOB_TIME_INTERVAL_DAYS: Long = 7 const val PURGE_LOGS_TIME_INTERVAL_HOURS: Long = 4 - const val PURGE_CONSOLE_LOGS_TIME_INTERVAL_HOURS: Long = 4 + const val PURGE_CONSOLE_LOGS_TIME_INTERVAL_HOURS: Long = 3 const val BLOCKLIST_UPDATE_CHECK_INTERVAL_DAYS: Long = 3 const val DATA_USAGE_TIME_INTERVAL_MINS: Long = 20 fun isWorkRunning(context: Context, tag: String): Boolean { val instance = WorkManager.getInstance(context) val statuses: ListenableFuture> = instance.getWorkInfosByTag(tag) - Logger.d(LOG_TAG_SCHEDULER, "Job $tag already running check") + Logger.i(LOG_TAG_SCHEDULER, "Job $tag already running check") return try { var running = false val workInfos = statuses.get() @@ -79,7 +79,7 @@ class WorkScheduler(val context: Context) { fun isWorkScheduled(context: Context, tag: String): Boolean { val instance = WorkManager.getInstance(context) val statuses: ListenableFuture> = instance.getWorkInfosByTag(tag) - Logger.d(LOG_TAG_SCHEDULER, "Job $tag already scheduled check") + Logger.i(LOG_TAG_SCHEDULER, "Job $tag already scheduled check") return try { var running = false val workInfos = statuses.get() @@ -108,7 +108,7 @@ class WorkScheduler(val context: Context) { // app exit info is supported from R+ if (!Utilities.isAtleastR()) return - Logger.d(LOG_TAG_SCHEDULER, "App exit info job scheduled") + Logger.i(LOG_TAG_SCHEDULER, "App exit info job scheduled") val bugReportCollector = PeriodicWorkRequest.Builder( BugReportCollector::class.java, @@ -135,7 +135,7 @@ class WorkScheduler(val context: Context) { .addTag(PURGE_CONNECTION_LOGS_JOB_TAG) .build() - Logger.d(LOG_TAG_SCHEDULER, "purge connection logs job scheduled") + Logger.i(LOG_TAG_SCHEDULER, "purge connection logs job scheduled") WorkManager.getInstance(context.applicationContext) .enqueueUniquePeriodicWork( PURGE_CONNECTION_LOGS_JOB_TAG, @@ -154,7 +154,7 @@ class WorkScheduler(val context: Context) { .addTag(PURGE_CONSOLE_LOGS_JOB_TAG) .build() - Logger.d(LOG_TAG_SCHEDULER, "purge console logs job scheduled") + Logger.i(LOG_TAG_SCHEDULER, "purge console logs job scheduled") WorkManager.getInstance(context.applicationContext) .enqueueUniquePeriodicWork( PURGE_CONSOLE_LOGS_JOB_TAG, From 88d36fce4b17828bda4e039896a5583eb625a1a7 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 11 Jul 2024 19:22:26 +0530 Subject: [PATCH 052/188] scheduler: stop console logs if its running more than configured time ie., 3 hrs --- .../com/celzero/bravedns/scheduler/PurgeConsoleLogs.kt | 10 +++++----- .../celzero/bravedns/database/ConsoleLogRepository.kt | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/scheduler/PurgeConsoleLogs.kt b/app/src/full/java/com/celzero/bravedns/scheduler/PurgeConsoleLogs.kt index 8a324e6cc..f3bd9ea10 100644 --- a/app/src/full/java/com/celzero/bravedns/scheduler/PurgeConsoleLogs.kt +++ b/app/src/full/java/com/celzero/bravedns/scheduler/PurgeConsoleLogs.kt @@ -16,8 +16,7 @@ class PurgeConsoleLogs(val context: Context, workerParameters: WorkerParameters) private val persistentState by inject() companion object { - const val MAX_TIME: Long = 4 - const val MAX_LOGS: Int = 200000 + const val MAX_TIME: Long = 3 } override suspend fun doWork(): Result { // delete logs which are older than MAX_TIME hrs @@ -27,9 +26,10 @@ class PurgeConsoleLogs(val context: Context, workerParameters: WorkerParameters) consoleLogRepository.deleteOldLogs(time) if (persistentState.consoleLogEnabled) { - // check if the logs are too many, if so stop the logging - val count = consoleLogRepository.getLogCount() - if (count > MAX_LOGS) { + val startTime = consoleLogRepository.consoleLogStartTimestamp + // stop the console log if it exceeds max time + if (currTime - startTime > TimeUnit.HOURS.toMillis(MAX_TIME)) { + consoleLogRepository.consoleLogStartTimestamp = 0 persistentState.consoleLogEnabled = false } } diff --git a/app/src/main/java/com/celzero/bravedns/database/ConsoleLogRepository.kt b/app/src/main/java/com/celzero/bravedns/database/ConsoleLogRepository.kt index aac30738e..5145ed783 100644 --- a/app/src/main/java/com/celzero/bravedns/database/ConsoleLogRepository.kt +++ b/app/src/main/java/com/celzero/bravedns/database/ConsoleLogRepository.kt @@ -17,6 +17,8 @@ package com.celzero.bravedns.database class ConsoleLogRepository(private val consoleLogDAO: ConsoleLogDAO) { + var consoleLogStartTimestamp: Long = 0 + suspend fun insert(log: ConsoleLog) { consoleLogDAO.insert(log) } From 6395194f9e8dc8ba44fdf2124a07179125dbf7b1 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 11 Jul 2024 19:25:14 +0530 Subject: [PATCH 053/188] ui: limit number of custom ips and domain rules in per-app ip rules screen --- .../layout/list_item_custom_all_domain.xml | 22 +++++++++++++++++-- .../res/layout/list_item_custom_all_ip.xml | 22 +++++++++++++++++-- .../bravedns/database/CustomDomainDAO.kt | 2 +- .../celzero/bravedns/database/CustomIpDao.kt | 2 +- 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/app/src/full/res/layout/list_item_custom_all_domain.xml b/app/src/full/res/layout/list_item_custom_all_domain.xml index f6bd8c38f..8f8d80886 100644 --- a/app/src/full/res/layout/list_item_custom_all_domain.xml +++ b/app/src/full/res/layout/list_item_custom_all_domain.xml @@ -11,7 +11,7 @@ android:alpha="0.2" android:background="?attr/primaryLightColorText" /> - - + + + - - + + + } diff --git a/app/src/main/java/com/celzero/bravedns/database/CustomIpDao.kt b/app/src/main/java/com/celzero/bravedns/database/CustomIpDao.kt index 31cdbc0ad..6db932286 100644 --- a/app/src/main/java/com/celzero/bravedns/database/CustomIpDao.kt +++ b/app/src/main/java/com/celzero/bravedns/database/CustomIpDao.kt @@ -97,7 +97,7 @@ interface CustomIpDao { fun getAppWiseCustomIp(query: String, uid: Int): PagingSource @Query( - "select * from CustomIp where ipAddress like :query and isActive = 1 and uid != $UID_EVERYBODY order by uid" + "SELECT * FROM (SELECT *, (SELECT COUNT(*) FROM CustomIp ci2 WHERE ci2.uid = ci1.uid AND ci2.rowid <= ci1.rowid) row_num FROM CustomIp ci1 WHERE ipAddress LIKE :query AND isActive = 1 AND uid != $UID_EVERYBODY) WHERE row_num <= 5 ORDER BY uid, row_num" ) fun getAllCustomIpRules(query: String): PagingSource From a27ecd9f79fd6325fd227c394be1d23676148422 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 11 Jul 2024 19:26:43 +0530 Subject: [PATCH 054/188] ui: rmv observer for the connections count, let adapter handle it --- .../ui/activity/AppWiseDomainLogsActivity.kt | 30 ------------------- .../viewmodel/AppConnectionsViewModel.kt | 8 ----- .../bravedns/database/ConnectionTrackerDAO.kt | 5 ---- .../bravedns/database/RethinkLogDao.kt | 5 ---- 4 files changed, 48 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseDomainLogsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseDomainLogsActivity.kt index 8c94e5630..bb7a5da37 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseDomainLogsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseDomainLogsActivity.kt @@ -78,12 +78,10 @@ class AppWiseDomainLogsActivity : isRethink = true init() setRethinkAdapter() - observeRethinkNetworkLogSize() b.toggleGroup.addOnButtonCheckedListener(listViewToggleListener) } else { init() setAdapter() - observeNetworkLogSize() setClickListener() } } @@ -237,34 +235,6 @@ class AppWiseDomainLogsActivity : } } - private fun observeNetworkLogSize() { - networkLogsViewModel.getDomainCount(uid).observe(this) { - if (it == null) return@observe - - if (it <= 0) { - showNoRulesUi() - hideRulesUi() - } else { - hideNoRulesUi() - showRulesUi() - } - } - } - - private fun observeRethinkNetworkLogSize() { - networkLogsViewModel.getRinRDomainCount().observe(this) { - if (it == null) return@observe - - if (it <= 0) { - showNoRulesUi() - hideRulesUi() - } else { - hideNoRulesUi() - showRulesUi() - } - } - } - private fun showNoRulesUi() { b.awlNoRulesRl.visibility = View.VISIBLE } diff --git a/app/src/full/java/com/celzero/bravedns/viewmodel/AppConnectionsViewModel.kt b/app/src/full/java/com/celzero/bravedns/viewmodel/AppConnectionsViewModel.kt index 9e78d8aba..14df5f319 100644 --- a/app/src/full/java/com/celzero/bravedns/viewmodel/AppConnectionsViewModel.kt +++ b/app/src/full/java/com/celzero/bravedns/viewmodel/AppConnectionsViewModel.kt @@ -178,14 +178,6 @@ class AppConnectionsViewModel(private val nwlogDao: ConnectionTrackerDAO, privat return rinrDao.getAppConnectionsCount() } - fun getDomainCount(uid: Int): LiveData { - return nwlogDao.getAppDomainConnectionsCount(uid) - } - - fun getRinRDomainCount(): LiveData { - return rinrDao.getAppDomainConnectionsCount() - } - fun getDomainLogsLimited(uid: Int): LiveData> { val to = System.currentTimeMillis() - ONE_WEEK_MILLIS return Pager(pagingConfig) { nwlogDao.getAppDomainLogsLimited(uid, to) } diff --git a/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerDAO.kt b/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerDAO.kt index 01d2555fb..bd52d4ef5 100644 --- a/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerDAO.kt +++ b/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerDAO.kt @@ -125,11 +125,6 @@ interface ConnectionTrackerDAO { @Query("select count(DISTINCT(ipAddress)) from ConnectionTracker where uid = :uid") fun getAppConnectionsCount(uid: Int): LiveData - @Query( - "select count(DISTINCT(dnsQuery)) from ConnectionTracker where uid = :uid and dnsQuery != ''" - ) - fun getAppDomainConnectionsCount(uid: Int): LiveData - @Query( "select * from ConnectionTracker where blockedByRule in (:filter) and isBlocked = 1 order by id desc LIMIT $MAX_LOGS" ) diff --git a/app/src/main/java/com/celzero/bravedns/database/RethinkLogDao.kt b/app/src/main/java/com/celzero/bravedns/database/RethinkLogDao.kt index 51a664303..46e11f3eb 100644 --- a/app/src/main/java/com/celzero/bravedns/database/RethinkLogDao.kt +++ b/app/src/main/java/com/celzero/bravedns/database/RethinkLogDao.kt @@ -85,11 +85,6 @@ interface RethinkLogDao { @Query("select count(DISTINCT(ipAddress)) from RethinkLog") fun getAppConnectionsCount(): LiveData - @Query( - "select count(DISTINCT(dnsQuery)) from ConnectionTracker where dnsQuery != ''" - ) - fun getAppDomainConnectionsCount(): LiveData - @Query("select * from RethinkLog where isBlocked = 1 order by id desc LIMIT $MAX_LOGS") fun getBlockedConnectionsFiltered(): PagingSource From 06bdd974b8c851e58684bb3eb56702adf46538c2 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 11 Jul 2024 19:29:01 +0530 Subject: [PATCH 055/188] ui: rmv observer for the ips count, let adapter handle it --- .../ui/activity/AppWiseIpLogsActivity.kt | 28 ------------------- .../viewmodel/AppConnectionsViewModel.kt | 8 ------ .../bravedns/database/ConnectionTrackerDAO.kt | 3 -- .../bravedns/database/RethinkLogDao.kt | 3 -- 4 files changed, 42 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseIpLogsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseIpLogsActivity.kt index a3d8613cc..92155672f 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseIpLogsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseIpLogsActivity.kt @@ -223,34 +223,6 @@ class AppWiseIpLogsActivity : } } - private fun observeNetworkLogSize() { - networkLogsViewModel.getIpCount(uid).observe(this) { - if (it == null) return@observe - - if (it <= 0) { - showNoRulesUi() - hideRulesUi() - } else { - hideNoRulesUi() - showRulesUi() - } - } - } - - private fun observeRethinkNetworkLogSize() { - networkLogsViewModel.getRinrIpCount().observe(this) { - if (it == null) return@observe - - if (it <= 0) { - showNoRulesUi() - hideRulesUi() - } else { - hideNoRulesUi() - showRulesUi() - } - } - } - private fun updateAppNameInSearchHint(appName: String) { val appNameTruncated = appName.substring(0, appName.length.coerceAtMost(10)) val hint = getString( diff --git a/app/src/full/java/com/celzero/bravedns/viewmodel/AppConnectionsViewModel.kt b/app/src/full/java/com/celzero/bravedns/viewmodel/AppConnectionsViewModel.kt index 14df5f319..4435d37d5 100644 --- a/app/src/full/java/com/celzero/bravedns/viewmodel/AppConnectionsViewModel.kt +++ b/app/src/full/java/com/celzero/bravedns/viewmodel/AppConnectionsViewModel.kt @@ -170,14 +170,6 @@ class AppConnectionsViewModel(private val nwlogDao: ConnectionTrackerDAO, privat return startTime.value ?: (System.currentTimeMillis() - ONE_HOUR_MILLIS) } - fun getIpCount(uid: Int): LiveData { - return nwlogDao.getAppConnectionsCount(uid) - } - - fun getRinrIpCount(): LiveData { - return rinrDao.getAppConnectionsCount() - } - fun getDomainLogsLimited(uid: Int): LiveData> { val to = System.currentTimeMillis() - ONE_WEEK_MILLIS return Pager(pagingConfig) { nwlogDao.getAppDomainLogsLimited(uid, to) } diff --git a/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerDAO.kt b/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerDAO.kt index bd52d4ef5..dc8a4d3c5 100644 --- a/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerDAO.kt +++ b/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerDAO.kt @@ -122,9 +122,6 @@ interface ConnectionTrackerDAO { query: String ): PagingSource - @Query("select count(DISTINCT(ipAddress)) from ConnectionTracker where uid = :uid") - fun getAppConnectionsCount(uid: Int): LiveData - @Query( "select * from ConnectionTracker where blockedByRule in (:filter) and isBlocked = 1 order by id desc LIMIT $MAX_LOGS" ) diff --git a/app/src/main/java/com/celzero/bravedns/database/RethinkLogDao.kt b/app/src/main/java/com/celzero/bravedns/database/RethinkLogDao.kt index 46e11f3eb..37e3d09eb 100644 --- a/app/src/main/java/com/celzero/bravedns/database/RethinkLogDao.kt +++ b/app/src/main/java/com/celzero/bravedns/database/RethinkLogDao.kt @@ -82,9 +82,6 @@ interface RethinkLogDao { ) fun getLogsForAppFiltered(uid: Int, ipAddress: String): PagingSource - @Query("select count(DISTINCT(ipAddress)) from RethinkLog") - fun getAppConnectionsCount(): LiveData - @Query("select * from RethinkLog where isBlocked = 1 order by id desc LIMIT $MAX_LOGS") fun getBlockedConnectionsFiltered(): PagingSource From 488663c7e91b093e1aebac692e439d1b5a5a921d Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 11 Jul 2024 19:29:55 +0530 Subject: [PATCH 056/188] ui: rmv separate space for app name and icon from app-wise ip logs screen --- .../res/layout/activity_app_wise_ip_logs.xml | 47 ++++++------------- 1 file changed, 14 insertions(+), 33 deletions(-) diff --git a/app/src/main/res/layout/activity_app_wise_ip_logs.xml b/app/src/main/res/layout/activity_app_wise_ip_logs.xml index 596b0e186..06e71625a 100644 --- a/app/src/main/res/layout/activity_app_wise_ip_logs.xml +++ b/app/src/main/res/layout/activity_app_wise_ip_logs.xml @@ -6,37 +6,6 @@ android:background="?attr/background" android:orientation="vertical"> - - - - - - - - + + + app:queryHint="@string/search_universal_ips" + app:searchHintIcon="@null" + app:searchIcon="@null" /> + From 093f06e63ac38a36971f447d9b1d4ff005066d47 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 11 Jul 2024 19:32:27 +0530 Subject: [PATCH 057/188] store console log start time in-memory to be handled by scheduler --- .../bravedns/ui/activity/ConsoleLogActivity.kt | 7 +++++++ .../bravedns/database/ConnectionTrackerRepository.kt | 12 ++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/ConsoleLogActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/ConsoleLogActivity.kt index 5307889c6..129d1e37a 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/ConsoleLogActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/ConsoleLogActivity.kt @@ -42,6 +42,7 @@ import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import androidx.lifecycle.Lifecycle +import androidx.lifecycle.ReportFragment.Companion.reportFragment import androidx.lifecycle.lifecycleScope import androidx.lifecycle.repeatOnLifecycle import androidx.recyclerview.widget.LinearLayoutManager @@ -51,6 +52,7 @@ import androidx.work.WorkManager import by.kirich1409.viewbindingdelegate.viewBinding import com.celzero.bravedns.R import com.celzero.bravedns.adapter.ConsoleLogAdapter +import com.celzero.bravedns.database.ConsoleLogRepository import com.celzero.bravedns.databinding.ActivityConsoleLogBinding import com.celzero.bravedns.databinding.DialogInfoRulesLayoutBinding import com.celzero.bravedns.scheduler.WorkScheduler @@ -78,6 +80,7 @@ class ConsoleLogActivity : AppCompatActivity(R.layout.activity_console_log) { private val persistentState by inject() private val consoleLogViewModel by inject() + private val consoleLogRepository by inject() private val workScheduler by inject() companion object { @@ -210,8 +213,10 @@ class ConsoleLogActivity : AppCompatActivity(R.layout.activity_console_log) { persistentState.consoleLogEnabled = !persistentState.consoleLogEnabled if (persistentState.consoleLogEnabled) { b.consoleLogStartStop.text = getString(R.string.hsf_stop_btn_state) + consoleLogRepository.consoleLogStartTimestamp = System.currentTimeMillis() } else { b.consoleLogStartStop.text = getString(R.string.hsf_start_btn_state) + consoleLogRepository.consoleLogStartTimestamp = 0L } } b.consoleStatInfo.setOnClickListener { showStatsDialog() } @@ -227,6 +232,7 @@ class ConsoleLogActivity : AppCompatActivity(R.layout.activity_console_log) { builder.setPositiveButton(getString(R.string.hsf_start_btn_state)) { dialogInterface, _ -> handler.post { persistentState.consoleLogEnabled = true + consoleLogRepository.consoleLogStartTimestamp = System.currentTimeMillis() b.consoleLogStartStop.text = getString(R.string.hsf_stop_btn_state) showToastUiCentered( this, @@ -265,6 +271,7 @@ class ConsoleLogActivity : AppCompatActivity(R.layout.activity_console_log) { builder.setPositiveButton(getString(R.string.hsf_stop_btn_state)) { dialogInterface, _ -> handler.post { persistentState.consoleLogEnabled = false + consoleLogRepository.consoleLogStartTimestamp = 0L b.consoleLogStartStop.text = getString(R.string.hsf_start_btn_state) showToastUiCentered( this, diff --git a/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerRepository.kt b/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerRepository.kt index 94b010597..b4617a253 100644 --- a/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerRepository.kt +++ b/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerRepository.kt @@ -20,8 +20,13 @@ import com.celzero.bravedns.RethinkDnsApplication import com.celzero.bravedns.RethinkDnsApplication.Companion.DEBUG import com.celzero.bravedns.data.ConnectionSummary import com.celzero.bravedns.data.DataUsage +import com.celzero.bravedns.service.PersistentState +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject -class ConnectionTrackerRepository(private val connectionTrackerDAO: ConnectionTrackerDAO) { +class ConnectionTrackerRepository(private val connectionTrackerDAO: ConnectionTrackerDAO): KoinComponent { + + private val persistentState by inject() suspend fun insert(connectionTracker: ConnectionTracker) { connectionTrackerDAO.insert(connectionTracker) @@ -32,9 +37,12 @@ class ConnectionTrackerRepository(private val connectionTrackerDAO: ConnectionTr } suspend fun updateBatch(summary: List) { + val d = Logger.LoggerType.fromId(persistentState.goLoggerLevel.toInt()) + val debug = d.isLessThan(Logger.LoggerType.INFO) // debug, verbose, very verbose + summary.forEach { // update the flag and target ip if in debug mode - if (DEBUG && !it.targetIp.isNullOrEmpty()) { + if (debug && !it.targetIp.isNullOrEmpty()) { val flag = it.flag ?: "" connectionTrackerDAO.updateSummary( it.connId, From 268ccef507e60efca6adeb34dd7b238cc9d7a0f5 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 11 Jul 2024 19:41:08 +0530 Subject: [PATCH 058/188] fix: #1587, rmv junk entries from the proxy mapping --- app/src/main/assets/database/rethink_v22.db | Bin 188416 -> 131072 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/app/src/main/assets/database/rethink_v22.db b/app/src/main/assets/database/rethink_v22.db index 60c1c8a724eeed1e89b57ff9a00834e604f122ab..40902c28ed93153fbfc3b675e6e59840a857b293 100644 GIT binary patch delta 2624 zcmc&$eQZTGdZD=r_93`@BhE7 z6<=`dk*VJoxqhT-Yc!kIs>kBdczP@mi!4n&IyME5k5;mi!!B?7zg)C@EUX&L@! z2FC1+X$a98p*R&K#TRE`nE-b{vrm8zPD8=^jz|67d-U;mnus7&d5NfaGL}k)lH+Kv`s%BNHpdwbYe{lkz(3uUj-d;nf-E>jVdn$F{*7s#)326drEoM&aEts;vM#5}&@Pq{(6w+zD zZh=yA$%c?uQA)WQs~#=0U1IhS?OSsDo8p9H3jr!#hW70nUPdBF*qJdj%E_E z=+Y0;IgW~AdSN|`K?NFxg?1cNphifm@OcINBq{&IHx)BPWe#sEP%N;gvDgYhA?es@ zHB+mQA6954O#VAASfPZRmCr)72{&DW0%QNiQbBzit5paJ>Jf+(;b|4TIHrQgs$7QO zRn0)8f8i+=Y6+7lgQ}Sz>2a*EL2oHxrQpjI!L^IvhID;cZxdg{*dZJs#^~uN#CL6= zRWfGz0IF6HCRv2GK0J_4B?dyngR#!mhl%|ae4~NbzzyR_-3p^}OAeOXL8R5P7dP3V zjPA~|1=_@U0iSWBK5u0z7T1Tn#1~Qdpqs_#-?lb4GB6bPXJYAioEW$WJB2G1k798% z70#K6%Nrie?urj*6EQuTqHcg6zz$K@fxTF#kjlQTns<9-vp*3_4g2E*i6y14LXW6r zW2CDTCmd#fK>>7#E-^lCE%Aht`eP|NkY9m_>B{#ySL0O&I4A~Ab?SgBih(~44~rTP z#6+C9X_l+F%?UYFWqcci!$jecE-1vqPRJLa1HwT8CI$FgC%DKQ`v5<5nm5djVu5DX zDBBHPB7yh|FRa0&X6BAn;vo&nC&-XF^4z!Nxx1@R`t>%qTogqYcmPm1HzGFd&%e^(|$gkUi(3$6UGG-$r28GGBRr9N2HjAL!(77sgs z{2;aJfkRNEsvhBh zg?~u+C@#E9ZjHT7GlqT!P&*I#Mp3&>T`NPBjJ=9`CCI_2EZ_ou3C}KoU$I?d^JL|Y z1Bw(rXM2KG;c+hnlp+}#2>-D{6Ox}Jty0an-0s6<9+dgKGE|ejZ;7BY@fc0RkCuQ-pgIf6p;dMg3`M|^LNu7ki{0+(J5 z%lD=FN3+?~FuzE6fN*o{7*B7vDlIbXr#Sg6G%EaWG=2u~>pjq_hGf{G!HT0IWvh&d z2i#Wvkv7R%sW>`(c)5q(9{ZzEO$7tr)QmG7D+{!nEJp*&m3v7^Bs2V{0U6rKe76jK z;D5lMMZsS}j{qrta!+wAL#j)E6Zq^J8N!;;*;xWFQNeWPNjF)!W<1qdEQylB3!5n= zXFE0CB*dWP9%TDW5S=z~s(gWf4mvR8risR1!tiz|G@80xN{I~3gg>J~ftCM;W=K{u zQeBmN3-0cM&6s=$R@3D$RRezg^%LCcv0B_Oxc#pAdDj{Kvdcl-5I9&t^OTII&hs)Q UJTNq>r}59{_{#au&U2{yFIO{^pa1{> literal 188416 zcmeFa349z`buM02Qmb2Q&y2^2!5EkA@w7COT59dG$79-(Y-_B|ZrQR&44P7RsZ~~Y zS5H^976XB{7zj&P!jeDm5(o(iFDygY5<*A_Bzcg8C4`WdkPs3AfrNw?^7scbNqFD6 zRn=Wxs@8be!|%#7QZ08m_q*qwd+%9pB`!>BrYf)Mg{)%A1HvtWBneNIWkC@CLlA^0 z{&#+k;G*8Xc?$lD`2Pv^eT_dTBRc+}03{ATN@)&Re`+M>k2wMy0geDifFr;W;0SO8 zI0762jsQo1Bft^(J|H09Mw@uHlm8L;#qOQ zBft^h2yg^A0vrL307rl$z!BgGa0EC49DyG%1cIW}uI836C20Q-r+-1L`$7Es5e2dB zKUEM^o_FN~M3I?e^=3wo-UG_?)Q+@{i=bACu_6py+} zOY3?%qm~QID+M#B77W!a=FhTM^1Ss(PSCSR${{nJ&ocsh+Me-@z01@$)Ivttj%Gni zMm3^&wUE^eL(_9fJ!cm5OhzqG!#t$iOtwh7hn&`*OZ0*o2VOtC3}sa{w-v)E7L**J zpG(P!xr98U8itZqjeRe2%B<>%)`?Sx&=Z|5?6O;?np}%+Xem|CqlIcAs-$MF$n)&F zV>&RSZ)mC^-z8tBBCWu7q7_UvgRYFTi;fq0yRGG;XYcSp>@a}2re@Ws z1|XxxLq(;aCfM)Dkkbacn_8ZCvddG1n~(HW5W1-r5^6>zcA+SpWYLHuyv3YaI_xb* z$r{C6y39d2)r@Xxs~Y>2E~4RuMXl$ICNF4tqodwaVDmqpJPJVCU8t^U>l+$zM-*3R zSPm4O)7E>&t;^2m1?JG|0?=&!)k*4XH#pp3w^^uaM8E^Xzw|$5TerUygYp zb{e&j&!sdaqo<9_`P_tc75PPP{{L}x!~+UgB7>2TjXq>*5FdxT>uB~C9ltc@DcbP#J_#garJ!%38a0(x9oVLFrdj}N$ay8FW+Gqnl-&6DV?8Lj zYS^x4jWSbVT;|Ng`I$tVU06-1`?6C?)j(~0-8hxy<_b5f6NM{UCZpxj4sqjaYI0pK znvs%Ipgn3D=P%S^FxS8<=*3)0O%=eW7p+VABCeGj1ZyPfDKhk*iI|r<4~9l+J-UWI z-z;b*s61D>bolPA-a_BJNTa(7K5wDfXiBAN23QnP@b(0|lbzdruXM_+0y8w*+36v7 zU6q;Kvp*+f8Z@kxq8e) zuxCz!IJrgw62d91Dup#wNyV+HSdJG>J#yMxVtD9~r(+xl*46Fgnxf^-tK0H8UDROb zhCiBltU;&e)f^T8n8=huqPSut3tFB5<~%eak9r7e*q9>N_TreNmdYwQjJ(45)WnQ+ zRreLAoGLD9=!g?=Ri#08X4fEP2yRm)i4E`xB>9|f8hPEU(cZvco+ZethOdX0l|rhZ zY*KL-Izzp4DijEgk8corG zrw#2xPIp`MjFMDVj3{PAQ_re}xs1}04$LTdn%XP;Ntq8VdO66;jU;~zEsF`jSW^g- zX)U)dC#>tp*FEj1|2Uhs-PAY*f|kOzEzQp=w20c4=^0iW`(Er6Q`JrNrJ?;EXdP|@ zRYufQ(hEx3h`hwfZG%}qwd`Gldd3CIZn#*_h(i3^3&^>g+R^V_)6{(<;zepr+g756 zsT9uPqC5$OJjB%f=73isIf!VOF^uebLd(wR5b+Rl=QT6(EpGwO7^o%GH!y_IBR1W% zjFS^URE<2-TgJ%wlO7~Jh^Sj;d8&0pge|@ed0Ja3D245a;we`8e3y4Mv|0e4nED#{ z?o8#fW4OEslHQuAC1st(j}cwT=*e|8l`Cdfuokjzn=5WJD|*vwWc|2 zGfZqhGRrxAc@2}{@+KCk%PUH9{jB}QC0E$Ix8Kt_!g~(2djwosfsj5+zdELNi8bNj zcufH9&7_gc=*3jRf>j=;n|&{Ls;v?v!f4k%4}wmg=2nxlXU6O6eDXEf`~UE_8f5EV z;FE!j{qKo^@A6;$|Nq_p`w`U{aI!c89D)C)2n5E2BU0xpYR;bG?**PKEJ&TtqurFB z?nC?%5)4S49h5*x=_@MMRtj_);@j`2mwKg+573LM)vfr)fv*cGspCcT#-(5q{{hdZ zrH(W`_pWNiPvRwtyFxELs~NVwQ;Jx{=S(e0YQ}!?*BhpVd8uQX)#0ZJ6aNt@qEg3w zlw!qL$?F9ZTRyh_7JvEHdg+kVF~usQIZOO)L1_GQspA|y@ov~XI!^IxNc%RaW0=yq zR!HLiYIv_8OC5vs9NLD}jJ6fcs!*CFGa5)2-x{lzmZgq;g!&0#jNrsvCv4BW7U?}^q+SEY_-s{XN+ zGUDF^e^z*c)DfcO<;Ai1hj-RXlTt?mJ#}VXuewY8VsE{)Bz4qNB4;(?B=*tJiGSbp zcIhip5>%rTFv5JdMEMojSmTbCq@2_5|^P?%+<}5_@=w- zr3+Hz5B$oh))k2V8>JMa$h-ZLlJXkcc8tUSd8}SKD@ESvm#Vy16+eZt?vf&puvDJ) z=7yRts2N>JiEr+zml9Ist$t;B6hq=)KBZn7lOn%JX&sy^IzsVpo9+?bDMeoHS7CO0 zRl^Qcd{^UN3U^D9m${38@}KlhMwGNPP?SOCmQ=;JJ-J>Qk|Hm0r(|LQyGHS2sOqQ` zc|NPkS(n?(N>W(@q_-Tcm&T zW=;Gk-bJOzLwEu#(q!yeI|Ayr+w)3QD}*O_Jn$=Q;Iy5l2_Dw;$K)?K17e58eLjY%lsgI zrR7hBN2CZy_D$u|m5P6LSG_bPMS6B8b*st6FQCK+q)4Yb*<Y!3ULCa^i2Cs+SvBeT7v5&c;k2{vzvE`H5XLhMo8iCHpl> z-hj@`;r&OB)Jt*KyWM(5@lTLlcO^k)C-?i?{!2LKdPM{5qL!H?{qQWQ2sKEnce6>h zQoYk?O6;gC#NS0FP41H1R@rI|g7~5Ex^Uh}YooHB)3>6I9$5SZwu-Q;^x(F#l8oYi zW(E;|<#4@p%2Sl$)_aH_##;FyJ4Jc&t2X|S6+kU&;(L+wjGfeJB=kc}?Garq8fr>> zhYN0Zl8gxhD%PC^Q~XsnIastiNy-iT45|o9;{S2%|Nl)F@|M zK=%J&Yg7ooDLmBn+ihm+w^~cMCqkh>=t5CRjp0B9a@k%p&Af5Ew>OnDD6T0rMsaR**Yi*jCqP`p0Sa`e zg!OJ2&zDoGkrYe6ck-TZZ!|Ot1M7=aUm#nWvlk}Q}Q&CxZ zA>EtO*LoF7)`P$y#AWrCYpP1S%+h%3-3~MgL)ojl>pi1nwaoToSG>y7Jx0w|B2U#* z^^L+ElP9U_v+Ovta%I5J3i$8=&wAl0%B%x=X&XJn#;;~@+?LfVIBCKu*EvrI5ISjk zsIL&_;8Rg0(Xx}#Q)^CY6nc{h3p=Moxf(dD<&V2YU%;-4jl$telhyS(jjND*b8V_- zKidBX!mkp-{}BE}_WO!OZUxA z%%txhT{~BtS&LnpolM_%=JLcw?sD#;Ioda{)jzozFV0-toYe;gr&4Rz2JfHIhSpbd zv)7Wj%w}plcJ2Nv_w`NZ&cv=RUKpCr4rNmDiZPrvL(9xC?=rj-p(pB_gw7jv7s`Z| zO~+-5XB>j#lh-EuK=Ibh!c=i)VRC!=+WPR^#D%_@?bEsKNp*1h+~Q1MBEEk4(p*n! zF?Dv}`oikDs|)e*!L4)g{%fo0wf?j7YwO$7*^9+w|LnGU<#gZl;%p|FyHHGCxx9A& z#CmZhd%4e-?usS4>^V@HtRw2%8TIj08ef`9*+ytT>KE4gfOX#t$eE@87xdWd#D(iq z)9I}%DRnE+KXPsA{Kd_I@sUMC(TA?hN4ndgZ?L>Fn&r$^}n56pO-T$HHA{$EegK9Jq1E zIQ4?vj+<$~gx!GI$->3u`w|25)8oZu?f%KBm50WLuCFI9_fMZOwDi`*RPmCbFV9_A zyFPWz(}1&=vsbTNHd5n5#pLzL;?>2AxzysB?fb`vuC4TYd%|f!#d)K2N^BB_O2z}0 zY2M|ZQM)6~OiT{WCI+`>uBE|7R|nB4{c{ugpw$`M#>__H@`e8OE9W-T+p%=&!hNGN z>1%T%o5OS2)y(N@$yER8Ol)d2Ke;$-+`o9Hcr|z5MrvR-zmiQBR|f9Od0?;h1la{X z^Z#$#Y6<^I_zmGgIM(*nw)eDc{aDH--YAX$M}Q;15#R`L1ULd50geDifFr;W_#q%L z7Hk&U502sM-T0X+tmx(>K37!0=lu}a5XHBQ?2lkLUp9#Kk3{?MFSh@Qf-um~EF3<# z|0urJ?gU~?&2J1lCCuVW?ft|3eaBbT-=j|Il zJ~-0XXZ<=J!%w-8ll9HQQKt|qd_mq-Kwn?%_~7U;3ZP%dNAXiG;BJIIwIAGnphCQx z?y)HQS5Dn4v8Gz7D>S>a#G>q9IZKz=EZpH#U{UB!J{o2J?BvY;-!>tH-x!{2`}elj z1#9a zdV%lArY{PsEU#7M(-8cB^ui3GJFDVjnS~5K=4X_Nor=xCCj|}#AR0Jdiw~mQ>+q)W zEg0ugI3-x6)Be9cd`<}eMfi8ZKYF?DLuL}#ukn50S=Ss|tpHB=~`J~$U z%H9x1?{AmF!v4nn$nUC{a^5M1g*!s~TRmmdD_3^s`+qIL^1t&f=UX1C^{+MdgZNhy zX8wXBz!BgGa0EC490861M}Q;15%^vpQ1>m-$Zv1r_;l&4Ak_V%b9V+epA*wcb|nMT ztTc(+PlzeS#0^}9>pon5HIBRYh#UEwe3uO8?JZ3;PUF!Vs~^Sj=%v+WZ$fOZVV*LNxTGrev~eN>ij0c+yVLcFf)*#TVro>)+Ehrej@UO5Tt7j? zhX_<9YutsKMn;QtOc2EXveVp&>#sX$j^gHv&W&^gcYh{kG*itNv<)S>eHagZUsTd* z*!m4^!+9*@@gt(Tg?h5iGwBc>zE-pj@P7)fUm_YwP(h5crWtF*R|oNUQ{2{zW^qM* zGH$NzN^<~D7esvNKfs@5+N<0Z|s&_Yc}}x!+Fx1Y9>- zkM|3L@L%@h+j0GMF{>pDx}mR{(mve(t(8o=4cDI$m8^1I&q=r9=A-t_Ex7)G-8k&U z1>qeo8ryLH1~EwoQ0Qc8f2EzN1=m06N=IKq*%DKQV%ne`dDgmwGl3u|VoFs~AzUqr z;E{YDa0PKURlbuNaC_XYMGD}iM??fgLCu*)J?;*b@1#21-XeleGOC2jKqC<)hCeId zKmNlJ;0SO8I0762jsQo1Bft^h2yg^A0vrL3z<)mkj^14-%nYbweX+68m63tIp^?%4 z&_R}zu7llG)z4! zC-vN_mM#{Qm5eIGWX`^97PaD#+g1cJs#bnVJq^vr(o$)ToT=@4EX(sARmF~9h$qM6 zQ}JYcZG1hRK6&z+eWx>ea@~;i4YeT0`ue*2`ue(LB}MK9-LmcS&@CgTS~g)wUWff% zHrEPzF})@mdRCS7Rots`7A9vIc5TFo!`dycP^lRWW@x(sB`MGxnUYHTEA%GRWU=7- z480FFIM&}o;Si%xwjCh4WkiC542xRFTF$7|DUzE=Rblyeh`6=Bxpz@29dr@F{3GNU za*)^)x%gxp|G5y$>-l0vDF6lXu#lOHg{aykCK*UjZlBa-dtSJS0u&J&Xa9(hLdYaU9cuK;T6CB=(r-~VK4}xY*4smeavCu(Sh+ zAIFflL9p$H5dPWl$HS?%8*PuY{Yw}=A^e9Uz!BgGa0EC490861M}Q;15#R`L1ULfU zAp!_1@%!lxK$|4qoFXY04?rhmC;pDKK@9@|XlbkxZdN}NxHkZ$ik;{2t5fPn15kO` zL_Z#7M_pe4N(w9EP3b43SO98=Muz20Djb+<1|baM2X6kKUoY}!KNJMo#@mE_=MNk# zjfcc|dngne8(z?I+wwRRu!_u7pYk-cl=4LtAs;ye+t}edPC(bv5l87&P&T7!&0H(4 z6b-ciT`g3dW)#s)y{4A!!DIZ*ZTBQ|J!RczPr)g*H>()lxb8gt!Ix08Z#L!t>U1~o>f&%8I|T(O=0v~nR}HasLPYiwij)6C^j=G zqc@*HHPMv}lx5Krt20XJjyw=(S~@}@TOhd~1h;$|1Rv@;SUM4sB8=d{87EpO4~25h zR5FBILC*lg4GrgiRw$|saAHkCsMjK5q4H`0D6)Q|ZZjMzt($UPM;?&gv~;8#sN_&L z4C>|%+yV7(-BCu}zyhhXN&nZe03EiAALa}CH8p8=+U&qogDzUjUF>{@#fDSUdo5Cw zjM)QKq@n4#UZyukB|W)})$`);%Lz6XO$gektD9d-UEQ@G`tiC$jJAFz(rOb{4JXc& z%~ha$T;x&KGHJm#jxHTxN?RMWW)XySfv{5!VX@db3NMp8*0oeZS?%<6Y&9jdJ9R}# zW%X-?UL}>%bH=h&DEd>&^;!k@TAXG?H?>SEiI4XhM;>Usa=e`o0W9jT7%vk`3kZIGxR z>?w8GB#w?sv8i-DxQ;Clo z`>eSIwg3wh84Ekqv3N!c=J4kZ*1$Qm2pR`K<47oUm{?(Sgb_r9R77MAl5SQUrqp6O zr>T%UHD%(`5iOm=5zlt_2R{h1dNF{G+5>2Gcy1~&ANOZE7DbilIU-7L*kyzY01tChJ-Z%A7@Vm$)2mB?V? zOpDk%8L_8`ST^g7_Fq;gfc9pIy@`d!<3fCc)n2Aw{JEtgEa=x3L-nSx?_ld4upu9W zg)}xAb8M-ObVs@)6b_8P=!^PEH1F!c2sO`=2z}!$)Y!55)tdF4bySs&@H^}htBi7! zp7zH}N32KG3m~%Y4Ipw1@?jt(vOX9YgJ_tRJG^ticR0^_imADH!hDomKk~pzwWz3* zKwSb$(8D3=6p4zF(Tin^drdBJ@cUk~N5y_y#xt15Qi?^(Kdso;*FRhinYZDU;%LBB z*Qrvw&Eg{?6BI^XlXefx-|HqYb}mz|K2l+DVg~UQpmqk*<3LCnA?YzPykJGD-z>e~ zmH4pz2Fm0$omx8LM9M>g*rJ3)9Ze&`mq6_Lfje%9p}G;q8$;uIc16pnDS6&nb-9+* z#&P*f5vzob6+M^MlWX{0Yjs_FEmO(c>r`de#HRXb_nu%@Kcg42ib+Rt^d|EwnI2~$ zi5(i2{WMou6Qp#k2VCzxssRiXd%)TM|4??Xyi$$;M}Q;15#R`L1ULd50geDifFr;W z;0SO8ejo_&{r?XHW?l(LfFr;W;0SO8I0762jsQo1Bft^h2yg^`s0cXU|NEg5!7Jwo za0EC490861M}Q;15#R`L1ULd50geDi;QNYzWB(6~{wV$XzBZW`&Jo}Ua0I?f1e!h~ z9z5uLGI=RBHqbpf@PHE-CfG0E$ zg``4vbXgx;UK$)iNbBwtr$hV7OBsko7M_1KXnQ{2W(D3erjo(B`)d@#@QhiE1 zRGms%ij53($3_Ny62B?bUcn$X=xTrC3&q0+ov%DE4Ucw@_ID4DxD#lN=bZ#g!-L(! zL*1jX?%}cSk-_dE{2uBa!);&pSid{dpm?y$319|c$N|)Mf!5fHWUk<5N{Lc^&oE4jnC5xD*>4?d~7x9`LC$^xDRDSHe*rv_hMWSKU-C4GeS>-;9iP z4-9t?jCJ=9b`SPZ1%m_K;D$jw^sA{a)LRB_Xrwz9LouV>Bg5SzZU)5h-I1!=KznSg zdvLIOz^9$T&xQsZ90vNk2S5oTtkGIrkM0II_}0+=F8WCJk{_tSmxN9paD6y?X>7PV zh6csZqSy#pJP4|XFdVEHOT_a(5Nbc*d{`TU3FDyr>U3j!RavnCcg8zI2f%OJm>=r{ac>qGJ0S-Al1yfKYz(2&k>R>2tk# z74mq>jE$o5a#j<#IiK=g@?+hGi;x@k=e?JJ)u_)%ZiqHIX_or?x(7f(^PRYCUrIL0XTC-Z~%A=13ZRt9UGxGAX^`l5pcLp}^$|3ooOWgk&&&WhK_y}C-w?l{Rf~ez2cI_rhdM##GJ9gdg^)tlwuJU7tGRS zvCFE65`Z7jXCP_JkESjRCHrIbz7v;3^3O^D>E+X>;#W|D#ZQuNr;8tH+~?}ZC7)J_ zZwl={XdMQzG~^y_;)}sicJ2K>IfoHIu7THx$9-TGX3;b!B^ww%6y1%fM#@SI}q0nOd;(RaSI+b4f)_QLreK$RtR5RDr zf*xHd;6ur&oY6}G5JvB&R#4MLd>*=YGabYC%yX;v2_fMiDI8Nj7@xK*Xq$ zOBHl26wDK|7j({oAzNAelsy-0neHy~X_ z>i^F#wSlt+Gpebqt5Ns|Y-mYUd?At5M!(8GcXb4$^C%)hMK~mxYid@FYVZ??nku{o z3VL3AIkFtvACQik3{eNBCtA+5d zgm1?m{=*UA2yg^A0vrL307rl$z!BgGa0EC49DyGo0?(9MgoCYDTT8cw@H4ftZkWYF zcFi>Nr0~$}`>3&<(=&Q{JDOE{vEA=^sHhgU=O*-XvZ)l(swtE6Jd_@5x?!p*d1YI6 zii*nE1S2ap{JJ1YKRTM%ai{_f-J{u}v&!S+Z*eE5*5tYp;1^f(SK)Y3p&HL7mz7{53f zUzn5^;-{x4<*HX55iJ#wr)C!>&rV*HXXh5=*-O*Y-Et(SWYvhgFnI+}Do^QXL@hn9 zm}|S=S&hA@8hJfu_~g}$c+%81@W!V)&6rRr8UyX{d#@SO&?2Su&D^fc^2Fqs_@(Iu zxvyK+jB&+GuEjH%J(KA3BFEa|XXUT2fcUe89t!ip&0!MEyXv9RjyL&gTV~2z7M~{ln z9b>po=nD?4uFHefsJh-fZcvMb%=Zxt$mhTTFkKTobm z4uIbKCs(29efxO1)6^kmcSGrmZ(iBXLg|_?6|)HL+|3qXy4oUQ7thQbYG_wZh=K++ z{g&}iMl;oArD$R-;(nQ75YOi`T2e6)7B+*ZFesasV>MnJxDk0uu>HgdamSKEHImhM z{G=*SYb5H36l)0EJZ38;tTUj&{TJrHa^a6tUY0X8=6$M&E4 zDyE6pPf3=!aQE~Q1h-O)A8K(I#L+euXIWI>JL*<37j7zzjVnmdVEg zAwrR)>4IL&rEV%ctvE8ek~+O@!j$Jotn!M(wKOW`Z!Vqe8f6{cLDtzh^2CPr^T%rO zmo+6%=vk~@mt*d`C*J68w^k+x7^k`4xv$%+dCvU`j3-A@tnA%A>+qJV#aKf4Ei#PS z;@#ZN`_GBanqk3zT1e5-P)*f%i$xH{{ra)C3v0QiZqzAnN{=tm8p=KT| zx4g1bc#v79sRdIhV|P9pg85yKUBKBDcFT4Zot@|RHMB2})fySt9w-?f)cl|C#|PQ6 zKR%NZZ^@4|0ulPR*C45xbw)3;wXucX_I_D~5sCIq(E+EWb->rWuze zK#9+6ax>leU`>|kykRynecQvUr6b!{VhIIXhc_Te$eWU5%GkZVR@Ljfpp`t-1e>&j0D{ zRWv{7pNLqYvcPor=nrsrdm~*RY(IEVeArS*SgW+Dyz{rAx*^VEBE8les^#OU6s$x( zyYGB0i41vd>Pg*a*}mu8YpHyEW$mVxNhM)s^L<$;V&%?em%XL5N_Hf_5~?O$0)pm@ za=U3lqLx|{lhv@!&fY*n`}o0HdISeqN4qwahhbLedpsOH9&Tt~jMb`O_o9kaJ8aYD zT1VY_Bi|Hk9~lv!`|K)dcTvqKWy_8u@~R%TRC9`Nn(s>ZvD)^QfGo+LV$dS_M{57l zxhOV}87;{Gw~zW;&cYg$f@aNoUsnaQ_pRKKA$^o;Dg4O)|32Xtgz%q+Umacy-x-$L zKGpW~ZCh>QZB4D8X?;s;sr9L??Jb{fc|*&CEq6A5qxnP4FKfQgd~4IEnqJ)VW~;{3-MRPhs4ujK=>4hvVOXEw4ikM zj@HK3$WV=w4vmLkE>{!mci+xhP&(N~$rH|5hc*ULeRV@qH`Rh=U5341Ue%LDWG zC@mdEmX3Os#dCNgf@z{;K48Dn7|Uv5QR_LQ$qUdjckisCzN5(8`8>+Jrmb6tJ<^qJ zFgT~J_rL*yYzMbXcZvhI5dG@<=XC)|&5eSiA zbf@3Q=MeitzA~5dxe4oPVn+!|S4XH3&6F0vXYdynrskYjX}~>)EIVszCJ)5fwS11< zI3$kmJQ$QNjZu|fqhh>=X2O~jHC77xCKlsrDy=%roI-$10i6OHU{1~S?kt1w9+YAq zZn>UC6p(#FCoGq+>YkrT#My-f&EU?{gVH$Vh?H2pI1aO%0d>L-lz~0gscw8tO|I)j z^PZgtg3<-*x(G!Um9w~x++@}p6;oT%yhw%|xba-XwL-^v-n;X(pp=MF_P6@A)<~^K z*FeT*K{HYNxyq&8qbGNkg3=;!_P$s87LbkNR1X{$YLJB=iv0<82QMh=_Snw-K?x>O z5b#vWzj1vts^oJSZKe8dcIRqP(oRsiPI}`&14k0Tu4L?vEo#x6vazBRqUNT)QZ!9H z*Ds%Df8|+aLrXL554@cULpxUh9Am9N@F!d;m|3k*&vj{djMueQE ze4f)+F;uQu*0^XIN1d*zN-A!3yBvqD{jQzML22j^a$9`iG-F-ePOia7bza?;$LZqK z&ZVFa{c54pUX@MC zbWxtr^;AYZymKKa^&UWujt{ULC`DU^zg*GKk_qcZ-kAqX`zTGC(&!l_sjMJ&j=9*V zg}ID!d}j`f$NJ&xz7^9rWx7sdO(CkMp&OPH)^*>`tSyiCz1W=@4m^cz3~XZ9v#{{O zbx)>e(6b)inE^@rsi7~SEKbis9vD$mNkiLg+_N(sl$M#y?x!>!-e5_g`Ym^s5rqgj ztLqqu@?1_mvU5HtMW{&~A6IynL zY{nRA=QXouX9|g#baudkd?dKAeRSt+Q0k(or(>9kSjCch4PJ_RRy|X>9N#$uxxOk+fuJq({5 zE@oFS@nCtd4!JeU?hHmOIQQcr?Kbi*)On~V6nfWN&v<0w2*N$*U+NeDATvoMakZ(=*A(Wv%H4vz@O zlw;_SRoZnXh34sYXeW+wUOnR6RIP17`+{XZQFd+vjk?SlDTKsv-x_13M?d7Br&k>)ailTTRZM8K2y_Cn!C@))D{0s$yfJJjUQt zo5ZRz4^a;pU`suldB^QrIe|oX?VJcoF=D<-A7fzHBYJ8k;f$iLo#PN&)V9bWH#zQw zh@MoH+#LP7cV{dpDO7A^&ab_+CP;!q;Ut^E++$ZaiPeE@xm(<`Gm1%r0PDEo(>^8` z;EJ_Iwr{v!>4kpC*vj3>oe_JIjXYGV{*0PRQZzN@c_lkNIU&<=+cH9=QQAE_!}cP< zf~CgNi22|ZHOm)IzBJ{4)w?qU9;H=V$E_G;g!lf1KuH9cSHBHWO_Se(7=+Zn`I ztL_H^>7tgIq#1RVM82jLaPM4p?+jr5L&Gy-@swxqWVctzPj_aUJfl@(Y^NW|X|=cK zA?Pz$y<$yv#y%5WW-G3fJ29|t9CQBccs_sEP85r6 zVxrCtMqEl?Q89xkSeeJ^T8<|R`if%SyVDbt=GbU@nY$H+YSJRkV6|X1iPdLPP1q0R zL@`Yw0<;>Do$jD?f=$tbQ~;K>s~K$zF6~%#Co>w@bk=&*vC{=8*f@GVYl9;s?WHcQ z($&KJ8a#+&J9mSNT=LhGbY&$O#s3iNIrH@Can@7AJDnJ&Y*GAfzv98-%rU)yjf{0+ z!_~jjfsAZ*@CeK3;ie5WUr@;b=`w_%E>E&ya@S4-eZ;2k`zX_ju|kZ1#U)#tEN)`~ zF+;1A=+0eWK9-R6L@~390?e#hD31pk3-kD2zDovFJv+xBm)J7<@2KSR094G?O^jRb zlS4aq2Bo8H{8(!@VvVffv;>!ZJ4Z1)Rd~=5|LZw@D_UMn;zlknDeu`i0x7aPi^Kjj zKw(gT3koJeIGz^c&JB)Q$QAF|IgGUdr>_7GhVB+ZcZWY6eoJ^KoC;5dpVIbE zZNJs_hPKVND{cL4b**1$eP8R#S~X|_ZfSY6<)bZcZh2}d>y{wnlH=%?*HJ^#ZI;0SO8 zI0762j=+Bl1niFuu@#g@*^-CGDu?}|Y8Lb2Ki?6Mdhk`ET}o>4@9Zxr?WJ=SKlkK- zG=S_s<1E1FJ4Li3$I3Yz#%{`$%HsRg~IUd69-n zO_8Drt6$oY7xT9v-4v>1N>xwOSerJXMJS}i(YpfDS)_Y~yLPq+WExg=RnK61ApVc8 zfOG-Le%hVPsa}P9d?xBpD7@T;WUp9r6z?V0#3Olz4q@&2OML9MN#FixU!q@EvZu%F)_Vjedo~apR-VxplsW=0=HduPF z2Gb=#bDuPze#{vuBf1GGlh3!?9Z~cvI18R;dvUSTmatc zEm)BJ^weBI&txd3Gpes!LOs?Okd{EyHA?Q0^N?;?4SR4MB*EDmS=EB@JLt42ln?bD zl)kQk?=Tdo$(U=0(AY5~Tk|P5ZLHq6*Xyhy+k%uCUp!QSama2#vz28fhkS|91zHaCDK+sL+5aa6od5qP;kSkh;fc0K+uqowwhgrYN9(V* zzM%E~)}EIC+wyBIsg_vtmz#g3xzv26xv%NbrjInezDa9(LgRZIw;G2+{}uXZ=oO)- zh1!E(3cfXH1jmE7H~eYCdm9Q3ryBMJz7Tk0;A}vu|K0i*)X&uKtNVQ2BX!TKQ|d;g zf0W)Oy+F!HL*hS)Zxyp*O!#Mz;{KU|(c!`c(&RDSOj(yhcIMPJ9J{n+D&^>$sl9ur zh{G&#l~hBPGSN}wI1&%cn`iQ-sM$R{?mW3Pm9R`z$6={B&$LSmtRp-_&nAj&D2vXt z$&R6bq%}^$W^ut)y^EuYFv4zV1rvsJLtizaRk}2Fdi=8WBw;-{1@p!Dc$s>K>hkn# zt!9X-8n4bgH7KooW|O&tb^POh!!TW6Vx<2>vYDzoywhw^zlpN0{_+aLkpRe;rD z7A9=+6`5hr$kl*7l&z-`*fd7S6kFXE_=8w%GBs~wbrj}|A*RniO0{C`3H2o`b&xW7 zjKquVJ_3`)7^!PJ+9=UAO}DV8s})r-jr%k`p(q=Ki$6%63FqNg1*CEvL<3R5a6JUjr+b(KU8-|rd z@I5e3FoVzS)>6omm95ar+Uq1lDYFK`IEWKivg$Yt6jzz5^(Lp0x%0BcP;zCCxe6=A z0}l{pzs`8eHL{7Xl4}LjWLea0i)2~B?DS!+bf;jk6rzYMNqg=;390SYiT;8+(}Q)y_*)&}4p z%o_t?l+nRlo6JpE1S$rBD<@&eNH9x0qslwdD^)P+Fm&W93VUGBunY*Fr$&{h6g9hI z*#)YfL}AIW@ckyk*W33r7ufSI&frO|kHDaDp)w?${}LY3u$BM5D=va@v)Y8b_4CY z;!lB4ouq8j1gF!3*p#s;Fp3MD9?jOQwcg!x5JfPLf~T_{aj>z)T^idr%xuP4{FUe6 zZIsonLr6NvKK;pXNC>j*$K4c^O`4YLUByGI?2rJrl7jM>?g$qS1SOpnKj5aWX7N}X zFFRG#@NgJ>66OXrt0de-I`~k}rX2Uo>9S{LdiXL1wVm@OY5 zg4FBRaB7#1foCXKr_9mFOVCwhKh`=VM6HFZ(@YqADVysya=X2dsQU-^n-#>wn!0YT zopvtn*@rPqQ~$o7r-WXiMjPgJ&AdQ+X6AaOLIhZ+RcydP>FjM-ww@=JeJpL5n+hLg zS?pH0Z0@Iqec6wU6)as~YAk=Z9F|7)!Y#;hiR$j9EY=FGf&tKiQ&cofvXw;r&7?UI zhG=2ypm+FDVck%V8|Yyj|3%82)pI(X{!pf96>GETD)9-M zbIz2nx>_Aa7Upi{^rG951vSIYPrrIvAjX(gyr$A(o?^2|++ruz)9fVw1QF$wjgst{ zKrhTIN#vkeK`)$cvS-lQgHoswO~s>htM-tf$p(uPt)LlG_DSvZ4F;+cvHi38pa!1vHyQ% z;DJC_{Wt1AQ2(6zQ+402`&iwZ>$b4}|4ZrDrI$*V#7D(9JU_%=3Wk)_dXkQ2wRKal)-|@-21W4mM>WrjOb5bELS+W zLR!9P+D`maIb1_|!aVwqKFj9c>h*+St|z8uWULf2nEd;m2?k;wT)#$`(1@q760$B> zI^@C(LWX24W1OY>z0UwEQI37qvdOayUD38ywxdjE8%-6n+3nNxK)y_N1MKIDE2pkw z4aIaSUv>9Fc_3!Bl%q;3k4JfCYNG7#cw!r_Ri}uT*|whsZghHKaup+k_IsGTOvXaK zFNU`;6VU|Lang24hLWgQz%pD#z&!rioNjn-2RDJ!5Ve{uhG?-^7WFw}HIwgmmJVf6 zpO`q`w}B-)+eE#QjTYCiBf;Q|W}0H*&v_0T{-L71z3^WvrIct&VcH8y+s*$1tJuR0OpY;KH2n3It!;T>4f4h~@la4oGO2gU%>%Whp3UmE&^wyP zc&EigMV3J=g=iz-FqOs0s&sL$4lWz1O5_X}TLzUC+Td;HR7I?DY%O7-dPYi zyfmR+f#WoVM1emB*P#Spi>QAk&{BqmSsWh!3Y{1eRgzF(B8UjPas)UTah%7}B~M&h zIC;%cY}|};AL~U@_RD2(?$*HfwD7GKB3TM&uy)Zi*xFlHC)W^OK`{99E{@89+iuac zbk25ecqy}or)Mv&rZKm)Guaz+^U_VoUfX5<=60%Mt5{kx0rYLE#DXMmcn%V_XskGD-b;zo5OK__dgK8xGGnelY}Ohcg)GGyj~pGh*4DY{xrw zC!phVVF~`AbuPNg=8FWv6>=s`8c>#v^wo%u1Z-IfmwRgC}MWno?$mf=8%G@>u9Iq;Xkzk zH9V&OR8n7v&z_x}7@xa3>#Sd&Olce*s3}9;YB(qKsFpfz25iYB#qF$hR-B2lp{JsV z_b;T#rE42|9l!}Q9ux*lp4~Z(0m>l#RRk$mw$dmJY@~b7=TBqxzY5^*Js*_PEEU_Q zIsnxK-kB&JT81Es zkji5882DwJC$IgvLKcTJLK4x0c^)d3DQrOT6XQ=D%tuM2Dh&IO)Q|L^r5tN*3?>-87vyXpgVe^K|A zx*K(=y79UvN?(^gBfVdGk@Pg_9%-NWP4RQ$hs2)~pDkV!XT_MfU-(zyvo4*1_zu?% zzx<&iV7VSz3ra^SIPo*$XDrvN%09p~a@lyb)}>ggBGOpP3I7*9biJ)oCH+d$UWN)k zbAa6IT(6*Egnt)pD21BWWWn%UNFNxEZlbwGWceJqhJ8(?CRI6&}VMu!nPAAtxIPckx z(EjD(0|9VBNechES8MDorqhMOCu{XKHNC8jAtj3N!o9#mayVm#$fEg{5LRQ0T#tgPB4b-N==URl)*c1lNh6?3p777X}o!2=<=CVZ-j`#p)- z`X|Yxn($jygUOQ+CKqeS{Zdu0dQ({{S-Zf$w)|hK5q8LAp79ommKP3G4dNj`2&!_d z!m9%8Dqix#v^pg;jp7B6uw0gzUTXdH1#8Cxl3pG)-W-_qtne3B@Pj|vsfVmF@Kb;RKDad!l{KhE%N5u^ zS$SZW{W>T1>bjMJmR`e#%rL67TVhkyRPAYydn12;)lz0xLTH9D4?y}k(&+L)KvHV; zAuQ@ndJ^XLR#*6l6))l6V6PSa*1g8MI~ZL2mAy!J)sI6%$P^%&dzDP-^{ge##Fy?R z%qS)7nTA1MFAy86mO38%C}~-WkqTRU<6g3lB~NWI2aptw_MoX)%P3u9QWI0*rUb} zdw^x!TY@D@9Vg?HRxpg!2B#87e$$|}`db_ZLznjmPC9=tzPsG*c9J@%SVvfh@2Hg~ zQ$8j`{8jg&%qd4BJE3he{0TSyP6GI(TPhau_FDqd0pbGtmH4VA;TyuD@IE2%!N3~= zn}MeVP6r@x>%UO{@%nexzp8$_?#=a!^{46&3GWl{#s2y~#W#jOUH5O{w}+n}z8vm? z$H2GS{Oe%nBf;(9MHW%;c0?51*ANc;Id-z`#Lr^2epAmG`z;jrIx9!~E%9SQh&IE1Wx%5P zEPIM(7D)%h3Imkgki~U`I`(>rU`p02Qw=yV2(c#nncl`00%awTqw#VMP%Hba>^d(< z{GiQcjF|F>@{NC(lq6vvwafKmtq!W49+)qSGxvO45X4be27Y;y2s||)Jp&!gw6`8T zw%4NL7hEL%leMwf_aeq29%Hhhtg7ZVraAaM!=5016+v8MW%0f1fJ5=GtqJWW&jl1e zYl}F>RE}EN;q=O3b>gS3*sace zhaQuGTkKMKyU*Yg|D>8&c0P>LkoVg%z_KSHf7yJUw}Lz?6<4h8M%5+&nQRpSa}q;~ z)IyT^-H0cv`rVb#et)KFTCcnplS2VX1$*8_+~xGFR|D@jB01tne8}36MUJ_pmh+ap z1}w)uoP%nvL3?eftXV1IMT?~(ckZsxI%`x%ERL2EzHuTTJp{s7G>k)7Suobrj#Idr zIAv{#B1dY~v4XP;*TrYu8;~|p#bI|V%2kw`V;^4WmA%62PX?qSvdivT%GqmDn9_^* z#Fg-Vo5n-#+!pXQ4s^DGkyNx(hFpR;y*gFUJGYvk?*aom-J+ zt^HEufV*NZJ7K2_N9gkDpu$Jd#x$yZd{&_IBLPW68}GP@su%@UK)Ub_o5UyHM82}# z?A`ZJUyG6N$X0XK8pRRIf@bSXE1*PiDlxVa8C49iC(~ zhWutjvihyq+?z>mscK$iZ728ndMYWKIOM_EUJL(XC$-$RJR36FA2K(03JDNCf!P2X zj6GfK_gPA!n+(V5wG>s4=`yfn6S;>Uo-^h2*#zv7lwoAK>^;Nc(#Lf06W(ZT{rA0w zI@-G^uSk-$ii%#|Pa_HxA zEI43;u6|3tn0EhJQ+D-h_#q1~unbzkPZE{R%xK{F?2Oo9t*7eW<=23eku}8q*1ooW z4+s08o#Y*Ug;)_2;eJx#O7+ZT%ORm^OIx*+h2M$r)+f*sQ1r&5j##bSbpWaO0b7~D zqFtEw5eJN0{=M*aqeuwhGq%BuC2^=I8!>JN7C7R>T9f*^g>FM9HRq(DC0%9jW+{%! zL8yZuK7#eDwGO6~Le~^iWfOQ=swB8{gIV(#EGZb~H+%PlnzYDunI~ekJ&p;Io5pTxs}J!y6mc8u|kN z5qKx;`WFMY*8gq&`|Dp?zfymq?(20QsryNs0T8XLlm1BhNvTi#w)i{Z!{UM{3;zIe z_WT*RfnX*pa_ghS%P9?RZtD>DGc5`f zt!warA&-pQS#}aY>;{6D*h!j^9lvoyctX-3JkvDbiN*d#Zy-A9EOUT)fnT2T0X5|D z3rTH^-9SXr962&a{@jN&Y*=s-F=%67dzitA<+L=-gHsU{aN-7nlE#_O3mY$9!Hvxp z=u^+D+pIkl6lNlyJkAVJ(Hn?Tnq+>cAM+#C(NV!8*9wKXbOUipSFaMOe@Gb|j;;(b z`!Fcydj@bUyO~t+8wl-4x_rAl({v4;JY0@!RsHGNi-R{1?8h@sh&AuCm@Ho$U_OTXy_!tFS{M~s2 zQ5{39^lmDhKIBN>!UdCUQfNO)J&ivnZy=~+`tVWFS&YKu+HryKGG{h^X%RtpQ|tFaQ|E>5vQd(K@6rGqijGCQ#|4&cj(FDTF{WxckS+1V0eE+#J! zL~Nzzw1W(I5E2jx)?_qgjq@|i{3u_>nM61tmf83mriqmheBnME!Zky=T|ek3ff+p) zr=xp@N{G2gP^Wi@PR-<9WBZuHHp<*l9sYr|!6G3(e}1Hdn2S@)SN?5PEmjMVwV<5< z8mAjZrORo(r$liVY|MR@a(V?EFhk6dQUF}+Z%>J$E=bNlmM&tBX(Xmf2)DQzNBWAU z1`Tzb27O$QCd+SS92za-=wS!ZXbG_v?6BfWS!I++`dGdl6bXPGojgC@SwhIgQ3lHv zHO@YV%7V=~^T3)f)DtBHTFjAG;!SLNF1X@=S;vi*5NR<^4#knrP)&|yg#rlC0T?nS zE-BQYOU6pI3DFocKJ|uQ1qArfVT_p<0U>=qCQ~GjLlg;PG48=5|G8QM* zTS83637SfG_kit?MW^Y|{?I;4;%Et>7%T*(!%};}Bt=oe(Y4fS^4!Fkag4v*`alVx z7fW=ee)YP_Ej>ZCdsR1{7>||^aS`*JWl;7-TX28m!*WkMTte8zF>H26X4M0flMLtf?JyS(GdzQp*X9Y+NJsTv&ft&QB*w2!=RI9RC;_L+fj2o_TJiv;vpd zFq51kZtkh6%J8c9mLEH|ufK%Ai1RE!94gMrjAd;ntIlCt1fziW;OR70LR^Hx#LnZ; z%Ro9qWj0bmz{Fhb#g83*X}S6&EWaOX{T44FWMcd*@xqrJ9&v`JYek!|re->y+*$>8 zr07s~Ob2SJbE1T3it{wQZBFqln_NyQY)ZOIHsPo*BSss?6Q~8*Ha(7)5LunywOpELilihnR{jJ04HO?Xe#%A-dv3?PJ#AWpV}q zATqvWz`oR?4wn#C!M?r~VH5AJ$;7jimuEDrncy-oT0%$#3)}9fV?f&=S%w-d2XStV z-~_YWAm*z4BBQ! zGnuDEg03LAb17S}MoNgiV6i#AC$mmME-k?62Lk(^65=ir6jtQvL0duU@tM2ixX{U# zgW9PQ;x3k%xc`WkKKSv`!fzl2^sLjk28R1+4Vfq+UKhm5h_W#6DIxg6O@OO~B%nOL zlg`+nNtw=r8Y&?&V~*CV`yN|i=$dAam;PUS-yU99Rp!0dx!;4LaxaHsIVVkX(^?Y_9Qt?56#VUl7_&Hy7v)=5n<+=@eu@JL_{v%$PA2rB8Vu8Fao0> zD0o8|2kIcgjE;zc&hLHKTIcMuvrm%F=Rfm2AmZRs!(k0Fz?{E~X@*wZVMvZy(_Q6@8@3mx4)5#2V@UvDBpqm>$8 zdndhT7!(kMeyY#XJ*f}!8v`V4HI6j!XKll;bpP)(?l-{x|M8lknndCmFaQiCn&N+q ze?R`o_&~f7jQ`(^T@~9OJ16>P^wH>L(F>y|MV^a%Ix-e%2>&{Kdw43`7!HP>41G2< z6j~d4NAT(3P00SYCh!&*0FMOL27>;lfb756f2QwM-#_|3?Az=+-TQOzz1}04ZN zpLy=_T;^FMj*4r=1)|<~1C5!}Kj#+D#C~HthaB5hCDNv6=eDHnd|pwUr{gbw#By@w z072+E_RXhVnFKfT-F<{M=uUTrOyGWI#50)2C$9}=n_-}a{tJbl_ z3rYjX+9X{t4rKhE>}2lX-Nn1+W?QD(no!@Dffl%d{s+yG;wtk?izh*_d^m4*YXq%u7&Gw{hpJN;c4P5980<== zO1GJdOWl#yBL1{5&3VOl2R*0B^F=+;Dz`BeOS+=?F1?$!QfstUtiXyCE9C@eqSDV~qZ%QD0R!nePC-&;4MVP;& z=^{lf)dDErYhg1$x%}wZPW7{{2-{Z+iCWg==E&cf)kSr~p*}WUmA*Fqh3almdtR+QQyEuVfnq|PJ z+$4pDm41_ILg~-uBFtTTY3j_`GsP+)CNNr%J+}y})*iNfT<<260^rriH-?B$?X2qM zPm=V}@*>Pxd+4H+W-qbhOu$&P zD8jJCT7mna4P^$WdwXs$hnsqGH+&L(U>6!(S%h(mnLtxd>0#0ni|3+B-l@-6vPmGy zZM>xjYZo)2svND3y_=uSO{W7(!q_2OjqUb<8Xg~`!)g=A z9G4Yg4ckpKzj)Ukvt7t0~peU7R+Uw z^Du4Ln*|VJe|AFWaovf(P`J*5u}r3Rn;Da_fq)rnEm+2+Y3+oHR}+myeGcbY6=$+G zt({@PY_@=`q?@*)@Z>r#`mby6>eRZVFYS#-Uy9xOed*aY{3+^ zhL7tPE1tw@GLoGHTzgms^wV!xK}Viu#j!! z9r2k8L95$A0jve1u%b-$wr%T4wsEjWg9Rg*^uavr_6+S7Fyi8(2QDS`v)O{NOmZn&Kz_ElFdsmhE4RnFHXtZs=hFPve)YL+6SO8Sl*?EZ{Ar(If+^lr9b z2ir?_$CSHAR_=)uK~Dx&I?zCo#y>HI#*yb)7T~!0B^HciEzD&30*Od(!ibebjWFiI zs!X=ZR#5-IoQuB}S}>Hc%_sF!SE1%9;R3AU#9FkuB$@qH*Df`?O{f0xW171mi}n6U`Oj>`l~)<;*`^w#p4uoJA+ChPxZL(lqV{I?>LASn@@-XNT<-80yo`LIbv|w%9 z$P52Fm5t;OBv5TuV4UY$FtpW`wKrTkv~5nFXLMh0w7?i5nKYiL zcs)3js<4=l1jPFgBQCNbgNoDEy6wd5dbaU=)+bzyYeK;Lt84xhepbblqo$ z)h&vNZLbAWSZ)`s$yez~$FRz&ByS7YaZm#O7D*Bbk_4Jr^1CErL2TdYj~807kjW+Y zYnQiA0Z}>8m)$cuu@gU5TQG#N!Ks!3G_Y9bb4b}Pa{%^DYt6?AmZ`TNhXg8dWu--+)%f^85W5f8d|NS+$1`gJY1TO>= zVA}I`sKb9+O)PQL|C7Wcp4$@FCk`cAefiM4eUBtg^t=>*HU7BomG~Fp*Lxn0=f(Q? z{`e|j;ZKkKBKDowy}>tvr+Ox0SI7Eeov}L4dE((%H2PZf+2E(5w+BN0&qn`Rd?|o750T*2t6FSqcU%;N(%BHF4C?vsVLu(_>#mx z%{6nZxKD;Z&7P}Od|swSnx0Nn+%02w(krNyP>!AHKgHimJan4xyN*Pe;&vHCmrm2u zRgz3Q-dSl?kuaPYayg4f-s$(88TZ%{5{rjqcuCsb_#tkRh_6{LSK=c|$aB2a zh`&_*chg73``_*NEI=zO6hd*ice9ZwcUZ!F9GSkwx7sV|fkU~&M{<;V0`uhRGvtvHB(Q?o8gr78kx)P(@! z&e{kNcR;xbonhzqZVeqB1I?4(G@g~fz4U`z5+_;iAlSSpd98R#2BW11>8nv(f(TP0 z^8lcVZ_B8S)PK5FL4qf$3kgQVUN62S(Rt}*RNQeip)}g>%y%!IP^kP_6dWGtuo>QwO@C#yz)e8Nzi4$ap(zoKjcrvd1_Z?sJ%njhqKu+(WCal z&(M>TFeq>n^!yXu5-ynf4Sns@Kn~|%DAcG2u}ST3w-btGc=R%8`Wb|clZE(Fu|d8k zy@cN5Xa{#5G^f6RDR&d!+}CdL!8ZMD}?+lHr*Au6NAW!$RT_A&m> z&h)iScDDA~0qj&6Bps`V1!WZX+bQGg>Z?DahbmZZ4NDGAtt(ti1k`p|M-O5WO@pe5 zkmO-F2JXH6yAYqSATH96@=O0jEK{8_wt40pLF=&0kl3v{Utyn`FeWvv{|FIVv8f94 zZXN~&pB!zFjYDL|GZaL$PkGu~3l#d7NHWuxGXBP@sU4>ACKZopOX z4pr@Re#QezVvFhZj;tvQRcETe2Zc`!!~&ipxI&PVnL#qfD{>8{&!(@O8IGFd5tL>F zq1y%qBR{}H;$)>fo<%Qllw-y;y0#UJJJp0am5RAhpnH_&;qi^Bye<*KEFT74qrE)?k)y%!N_2*exMEjNaAggz_jH zOa4Vyp5Vhc{Flv50nnw$|KDodY}DLaGg{M8b3)>~iBBYQiS>zK{L%Pd#Sg@n#eN&R zCpH{g8+&K;mFVZ97eyY4+z=UvtdEHB-QhnEFAn`z=z-8*g!TsC3O*QofAFH<>4EPC zZVem`)cb$p|Aar|Kg)O2_XXc2zUAK6z5nR_nD+p9{J!J4!gIhgU;IiuDTc*T<4rWd z<(~`8ZkWBh$sY50mb}A~!_zT4bWkZJZh6~a1J5o7uO^Hn1(eKXRRXsa=4RzaO&xR_ zR5kNyT_`6knKZNKnqAuecr3Ay(*L;6+ysnR2HkP@=eSe7xOLpUN*b(q?ymqf)E^UZd} zbLyf6btrA9+iZiuZYx!Bms@X33?DdLu-8rBmMRKtFgF1I(?oCkJGa8LC2}%hSne|W zxA!VDdYPi1I6XVeR#@lRjd}^a#Iapggh8ovp@8PA6rxG8QOg{n%gyyF>LhiMTVrSj zORqneQ*jUwTrHbF$y^uooXO_p^3lQPM~k@@)0W!hK6vcTmq0-^ZFe$ByS72DH(Ov< zB(H<}^Ce;_EtVcicAJE;ovzwBN+qsd zni&=HDNmOYa&wl*>UNH=R?MjN=DC>ZJUkEDU)F~xJY@n0*4So}U1c`G*iR0f)K_^N zY-a$C0gZVZC{o9>BVg;s+gD5XGe-|K*`^+`-`7rYr|Z{yQl@ zSZy}KdBqh!P1eXiO~IX<$^&RJ%q1{CCy}diSYLMu_mWb#k2I5 zg&>2`Wrxo+m%szaXtCw=d`X|b5Q;qjueqUSvtD7T({oa{^r9pe6|}Erb1}S749H>x zsNK*{^XfrT^|RQvfiqyw9-9p|pi*w`RSATVf+N5=3(h zCC@`r*+ChEvjLRj<~bNYnjr4&&_s~E=eJ4c`!;g{^dWA*JLc2?TOj}P#tnDkY!8NmiM(NNqX=tkAy3EGiZUVe2x%cI=*GCeT{2?4geEdjLygbqX zGlr?|y(Yk`lD*yScG3xa4;jsCO(0i07<+ zaIjUXiOKEB4xrPpt9IZs6Cl)Kb^x=xO25*1e2__&Dfw+%s>T4dn*gDXl8#$t&B#5; zoiPiRL&-&8h~T0L_N)~vVXA11opIb z`Vuc6UXb}X{l~8Cl_mhE5`2EGTeB*74H%rW^AHw5p~VCa)dhT6V!pId+5%J~nfobG zrSe#>G66_!XrqDr2Gy&lejax5eAd2!st2GJAb_oP$2lg@sB8F){JvX*hX;mx+Iw2Z z#+I7EpKd3WHuX1d1p&0q4V5DGJLs=Q6Zlg(MjxO@^mNguH#{lI1%tWBv42UTHiIA= zO<++O@tfN3RvDR+pmrmq8rrE$vbtdufV*sdnF$nX2kkoP87(wjBS&%<<`BnA+Cs_% zD7B4)!;ZRDr?^bpx3{i0fk55E+T_bz(xwCHZXqXtEs0wRuBo+>ZWEByeWbyse&|*i z9sH9Sun{W|2ASRA?9Wrsv5vASr$(ate<1MzqvnO0dura7cq4Hi{y6^6aS0rkz;Ov2 zm%woe9GAdx2^^QeaS0rkz;Ov2m%woe{Aneys)!(&Ub5!Q33-ZpzQUk37ZE+v&6cMD zx&>>4g>p_R5;7PoC&sk(0uVQ#rQiso_}C)iWiG#*F3}9{a6CR2bD>MEb9t<#h`1Z} zC(ViZyBBtiWbfAAWRK#Ct1BWDXB|5gs>5%4E`TG1pt$pjh`*_08@v;V+scq!%Kz8p zEgGRN-|Eo(;C}`m^FHDIb@0YuKN$C)3S1Y+c#DBG{@0M5@BjEW`y;+*eV+tZ;Eldy z&7^OvW=BoB#-DgO@j&9b#8_feVnO`3@o&aI9UqTxj3;A%h&>qGQqa=-H7sBTstoirf~tGO|CiGV%`a2s{fe!7IY&ha1BF(Dy_4go?gvd|BUG z->EL_V(RAIdt+*kP` z=|CHs$OBC-{#C(s8S-3Nj@&)<>>^7m};@tQx=XRURZTzhhhS6 zp6MZYewnOXXLGq`9p!TnsWtY8s`jYFDn;<3{p45;bOJ*g#0$Sdo?vKg0XneYf62plUyUk~9Cfwfp^?;nVfvE01DUOlQlY0&wM0DNgBoCzV zL1Jq^3AJ-D#H_XJnzhqzzhguPu~fisee9{#lqKqf{bfO&FVZi zRD!R$GWi*=VP*jeomyz0F(p5DDJ4V*BS!r4$L!ZLZjfx|sXnT}sfQu5186Of7!mPk z4s2GGgRL9&WL!#f`fwS6SrCt@_=^MfcIydI^8Zi-sJK{dh;wii8Gj`~?Wv2azjm~6 zhKlRi$AxTrwG7B74~?2u__ZLs5^*~>fSBGs}_3`L!X)zg738{5HxdRAxmN?^O-1Qo4_pQ+8lvVz>`+Hs6rpp0WY@ z)o{I+sfvx?E8TM=_sKSnx&@NSOI)u`lTK&3emc_Xp^@<~3)Iw?#T2|nO$mMW45_JK zQAW3prdwuoK+l^F8B*oWK_MiYZl$>7>KlgEi0>Hfc0JZ1#1 z3yuXB1_Obg2JQ=79ms|b*8HI6bKud5g+d|QrbmHyAlb-h^?n+#a`~XW5;o#5W z&&9t4HSLDrgYj&=D;nX_2Fdb&Ct`KdqdX*ngS>KkNO|=-{K$jw}CJ4ZDb7ivhOC} zq;IdU-WT>hkNAPBy#3x)L!WJ8_jsB#f8QI~! z;eyJllX`)^(t~ejOcKMYf**4QwoxyXm~xG0)cpSum2nq}<`ES)tBE~l;FfWBQr^Yj{rz$GSDrxy_kH8#&tQ7sh?w1o0Rp_=1_B=f3X zf6rbDzq?R98$VQi_#~Cp_i6oRIy}b#$P}lmP3Q02N;zV7jEB{_z1giWzcN#R0yCJc zSM&NNdqJ&2NPeiQZ_Z9>rt_@aPo?vd=tN@5?cY!D(lYzZEW->Kd7b8anS z^^wBE#ht2{#0op=1kaUhnz%z*&F*k}TNP?o@p0AAr(FA^&J=N_YQZg3$}#)UaqL>e zvM7pbtiDUdv{vNEPhh;RPP!`5EXL?=y?z+Xpf4j)SYb3DRhO-Q zp#lz>4GtSGE5Sd>l{xkWovcU1)V}b9y%xF!DH1Qyr;dro?FF>Xj`c2mW&{hzL4LR( zqDl|`XDUBa-X-x)Nmi12j2md1b%=IzC-F9(QcB9B^hgz99~#9i63v+s^VRwC4Jxe# zB=O3|-zt6o5&BM#uBI6i2h<_@b!XEY4RhKKnL4(ZcU)^vm-uMYOF zI_siR<)AZzF2Z|oOnpU{b50&uI3@;wrbhVnz;K~UHQ=FY4bUNF#ut>i=*v`6i$ZPZ zeHr4YS%NtC#n{*$w3nbjI^%vdJHF&ri{#-U(<)*xK*NTlz6;a}c)+cEX@^s$kd(T0 z-Oq(>6Qo2@CN5M%{6%}qB;~DP;;OR`0=v)oIMt(QaVhWcu*x7⪼8f4IcOcJy5cW zp|slJ|4eNx|Ic1oIZ2TaN(1~nSLib46khBWh-^HroETM~bbD%z8n=J4H%F?nlD<$J zf^gE$SjTS7u2uqV|(2hlGqHGd_zkYeki^l$HS!47 zA}3e>;R2jX{f8i`W~CI}<<^3VkYeL&Y8l_jMQxLnW}6OI+_1y=CpBufQ#m(EM3GT~ z{tT7S>#=?*F`s=Lo6`iSjl#(reUtKV}N*gI?B*9wMZ0$ls z&vc4-JQ|l z>gEu6PWe|G;>hxu zW(zbKk`2DotqPhQWs7v{&}3$+klZ=~HfGooGE>Ve@MLVYg(TaCu@n!cckL~TuJ#-Y zycxCSF!#vv!cVS(+UgDDcm+(dVH5UK$5VMR)!mUm&B1uBf5y)-XgcY91K zsJMdThS`IB3KL_Cz^Xr-O}2jx$9R5`q&b)XH4G@Wi3q;`aEk?Y3_H69MnR8xE}w%N z4Qol>^W~@~yYOjp7d~aOh$R-NF*w0Q>Pl{sZ4_w&;MibJp?r`2rNII#Mi;I1)YWdC zpr_yvfcKk7?o)*%Y+p+(kY8{u?wb&vA;LXu^H2o&mz5USFM9Yeo7HJ+ci^tDKz`B1 zG1woa);V?}hF3~D`&OxENp$5h3!E3NJfJg}RC;BsrjAK$w7_=3(VntbdRr~uMs?^p z5S&aXuSv$!7C0^%S%$b9mRI-TpC>sGll!n!aUm|Y zz*W)00;}q0$94w}2BcS6+?m^8fumv%oz`i;y$QM#O2a1&L9r(GfF=~d_^d-HN}^R3 zSSz;k`um;RODd@pLHJGH(O!giHd-L6*h<4RQ-D1tDfwJwh`m}wOD#}TNEYv^7sL&8 zOH8(BrsS=$)&ge*(>kOqZlz<6+n2`z3j`G`?#eNBj^tJgOccGnvTUo zX`xvcp|+K*>|`!g!(Hw3H5RBOq<%Zc>%HP;VEWx=3#<{G1hUdATwbh|4rksW%elB$ zT40T++lYp%9ZX#%dwX_jF;b4f#p9!k3zD8D%lRrBEig&!;*~xpBT*Zfi|w{il1$KuXW<MCrg-_xaU|^LAU{n_6MH1tJHT zU+Ls(<%wLkObNLW`5ISPAas!ISF-_W09oSBNnWJdCEh71Ws7WT@U~?Ot1R$0^zcF5 Date: Thu, 11 Jul 2024 19:45:57 +0530 Subject: [PATCH 059/188] new api for get system dns, net stat and write console logs --- .../celzero/bravedns/service/VpnController.kt | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/celzero/bravedns/service/VpnController.kt b/app/src/main/java/com/celzero/bravedns/service/VpnController.kt index eab126a83..aee3b0b6c 100644 --- a/app/src/main/java/com/celzero/bravedns/service/VpnController.kt +++ b/app/src/main/java/com/celzero/bravedns/service/VpnController.kt @@ -22,13 +22,17 @@ import android.content.Context import android.content.Intent import android.os.SystemClock import androidx.core.content.ContextCompat +import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import backend.RDNS -import backend.Stats +import backend.RouterStats import com.celzero.bravedns.R +import com.celzero.bravedns.database.ConsoleLog import com.celzero.bravedns.service.BraveVPNService.Companion.FAIL_OPEN_ON_NO_NETWORK import com.celzero.bravedns.util.Constants.Companion.INVALID_UID import com.celzero.bravedns.util.Utilities +import java.net.Socket +import kotlin.coroutines.cancellation.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -38,8 +42,6 @@ import kotlinx.coroutines.channels.consumeEach import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.inject -import java.net.Socket -import kotlin.coroutines.cancellation.CancellationException object VpnController : KoinComponent { @@ -101,10 +103,8 @@ object VpnController : KoinComponent { try { externalScope?.coroutineContext?.get(Job)?.cancel("VPNController - onVpnDestroyed") externalScope?.cancel("VPNController - onVpnDestroyed") - } catch (ignored: IllegalStateException) { - } catch (ignored: CancellationException) { - } catch (ignored: Exception) { - } + } catch (ignored: IllegalStateException) {} catch ( + ignored: CancellationException) {} catch (ignored: Exception) {} } fun uptimeMs(): Long { @@ -219,7 +219,7 @@ object VpnController : KoinComponent { return braveVpnService?.getProxyStatusById(id) } - suspend fun getProxyStats(id: String): Stats? { + suspend fun getProxyStats(id: String): RouterStats? { return braveVpnService?.getProxyStats(id) } @@ -239,6 +239,10 @@ object VpnController : KoinComponent { braveVpnService?.syncP50Latency(id) } + fun getRegionLiveData(): LiveData { + return braveVpnService?.getRegionLiveData() ?: MutableLiveData() + } + fun protocols(): String { val ipv4 = protocol.first val ipv6 = protocol.second @@ -339,4 +343,15 @@ object VpnController : KoinComponent { braveVpnService?.notifyConnectionMonitor() } + suspend fun getSystemDns(): String { + return braveVpnService?.getSystemDns() ?: "" + } + + fun getNetStat(): backend.NetStat? { + return braveVpnService?.getNetStat() + } + + fun writeConsoleLog(log: ConsoleLog) { + braveVpnService?.writeConsoleLog(log) + } } From 373f662026345e8aab6dd01ea0a38d7e7892594b Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 11 Jul 2024 19:47:19 +0530 Subject: [PATCH 060/188] state to store the console logs in persistent state TODO: instead use in-memory variable? --- .../java/com/celzero/bravedns/service/PersistentState.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/celzero/bravedns/service/PersistentState.kt b/app/src/main/java/com/celzero/bravedns/service/PersistentState.kt index 24d601a99..64e107be6 100644 --- a/app/src/main/java/com/celzero/bravedns/service/PersistentState.kt +++ b/app/src/main/java/com/celzero/bravedns/service/PersistentState.kt @@ -289,6 +289,9 @@ class PersistentState(context: Context) : SimpleKrate(context), KoinComponent { var pingv6Ips by stringPref("ping_ipv6_ips").withDefault(Constants.ip6probes.joinToString(",")) + // TODO: do we need this? instead use in-memory variable + var consoleLogEnabled by booleanPref("console_log_enabled").withDefault(false) + var orbotConnectionStatus: MutableLiveData = MutableLiveData() var median: MutableLiveData = MutableLiveData() var vpnEnabledLiveData: MutableLiveData = MutableLiveData() @@ -427,7 +430,7 @@ class PersistentState(context: Context) : SimpleKrate(context), KoinComponent { } fun getProxyStatus(): MutableLiveData { - if (proxyStatus.value == null) updateProxyStatus() + if (proxyStatus.value == null || proxyStatus.value == -1) updateProxyStatus() return proxyStatus } From da5cb62ffdcaf2f42253f1112f14812dea11fd4b Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 11 Jul 2024 19:48:12 +0530 Subject: [PATCH 061/188] conn-checks: rearrange the nws for conn checks from external src --- .../bravedns/service/ConnectionMonitor.kt | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/celzero/bravedns/service/ConnectionMonitor.kt b/app/src/main/java/com/celzero/bravedns/service/ConnectionMonitor.kt index 1cbe29ef1..4622a9034 100644 --- a/app/src/main/java/com/celzero/bravedns/service/ConnectionMonitor.kt +++ b/app/src/main/java/com/celzero/bravedns/service/ConnectionMonitor.kt @@ -631,6 +631,22 @@ class ConnectionMonitor(private val networkListener: NetworkListener) : return newNetworks } + private fun rearrangeNetworks(nws: Set): Set { + val newNetworks: LinkedHashSet = linkedSetOf() + val activeNetwork = connectivityManager.activeNetwork + val n = nws.firstOrNull { isNetworkSame(it, activeNetwork) } + if (n != null) { + newNetworks.add(n) + } + nws + .filter { isConnectionNotMetered(connectivityManager.getNetworkCapabilities(it)) } + .forEach { newNetworks.add(it) } + nws + .filter { !isConnectionNotMetered(connectivityManager.getNetworkCapabilities(it)) } + .forEach { newNetworks.add(it) } + return newNetworks + } + private fun isConnectionNotMetered(capabilities: NetworkCapabilities?): Boolean { return capabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_NOT_METERED) ?: false @@ -716,11 +732,10 @@ class ConnectionMonitor(private val networkListener: NetworkListener) : } suspend fun probeIp(ip: String, nws: Set): ProbeResult { - nws.forEach { nw -> + val newNws = rearrangeNetworks(nws) + newNws.forEach { nw -> if (isReachableTcpUdp(nw, ip)) { val cap = connectivityManager.getNetworkCapabilities(nw) - val ln = connectivityManager.getLinkProperties(nw) - val nwType = networkType(cap) return ProbeResult(ip, true, cap) } } From 230402dba71a336decf910564cd57f7de602dc29 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 11 Jul 2024 19:49:05 +0530 Subject: [PATCH 062/188] firestack apis: get netstat, slowdown, get system dns --- .../celzero/bravedns/net/go/GoVpnAdapter.kt | 69 ++++++++++++++----- 1 file changed, 51 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/celzero/bravedns/net/go/GoVpnAdapter.kt b/app/src/main/java/com/celzero/bravedns/net/go/GoVpnAdapter.kt index f24c2a220..5a40801dd 100644 --- a/app/src/main/java/com/celzero/bravedns/net/go/GoVpnAdapter.kt +++ b/app/src/main/java/com/celzero/bravedns/net/go/GoVpnAdapter.kt @@ -21,7 +21,6 @@ import Logger.LOG_TAG_VPN import android.content.Context import android.content.res.Resources import android.os.ParcelFileDescriptor -import android.os.SystemClock import android.widget.Toast import backend.Backend import com.celzero.bravedns.R @@ -36,6 +35,7 @@ import com.celzero.bravedns.service.ProxyManager import com.celzero.bravedns.service.ProxyManager.ID_WG_BASE import com.celzero.bravedns.service.RethinkBlocklistManager import com.celzero.bravedns.service.TcpProxyHelper +import com.celzero.bravedns.service.VpnController import com.celzero.bravedns.service.WireguardManager import com.celzero.bravedns.util.Constants import com.celzero.bravedns.util.Constants.Companion.MAX_ENDPOINT @@ -57,9 +57,7 @@ import intra.Intra import intra.Tunnel import java.net.URI import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.async import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -120,6 +118,7 @@ class GoVpnAdapter : KoinComponent { setRDNS() addTransport() setDnsAlg() + maybeSlowdown() Logger.v(LOG_TAG_VPN, "GoVpnAdapter initResolverProxiesPcap done") } @@ -145,7 +144,7 @@ class GoVpnAdapter : KoinComponent { // on route change, set the tun mode again for ptMode tunnel.setTunMode( - tunnelOptions.tunDnsMode.mode, + tunnelOptions.tunDnsMode.mode.toInt(), tunnelOptions.tunFirewallMode.mode, tunnelOptions.ptMode.id ) @@ -406,7 +405,7 @@ class GoVpnAdapter : KoinComponent { private fun setTunnelMode(tunnelOptions: TunnelOptions) { tunnel.setTunMode( - tunnelOptions.tunDnsMode.mode, + tunnelOptions.tunDnsMode.mode.toInt(), tunnelOptions.tunFirewallMode.mode, tunnelOptions.ptMode.id ) @@ -419,7 +418,7 @@ class GoVpnAdapter : KoinComponent { } try { tunnel.setTunMode( - tunnelOptions.tunDnsMode.mode, + tunnelOptions.tunDnsMode.mode.toInt(), tunnelOptions.tunFirewallMode.mode, tunnelOptions.ptMode.id ) @@ -737,7 +736,7 @@ class GoVpnAdapter : KoinComponent { } Logger.i(LOG_TAG_VPN, "remove wireguard proxy with id: $id") } catch (e: Exception) { - Logger.e(LOG_TAG_VPN, "error removing wireguard proxy: ${e.message}", e) + Logger.e(LOG_TAG_VPN, "err removing wireguard proxy: ${e.message}", e) } } @@ -760,7 +759,7 @@ class GoVpnAdapter : KoinComponent { if (isOneWg) setWireGuardDns(id) Logger.i(LOG_TAG_VPN, "add wireguard proxy with $id; dns? $isOneWg") } catch (e: Exception) { - Logger.e(LOG_TAG_VPN, "error adding wireguard proxy: ${e.message}", e) + Logger.e(LOG_TAG_VPN, "err adding wireguard proxy: ${e.message}", e) // do not auto remove failed wg proxy, let the user decide via UI // WireguardManager.disableConfig(id) showWireguardFailureToast( @@ -789,7 +788,7 @@ class GoVpnAdapter : KoinComponent { val res = getProxies()?.refreshProxies() Logger.i(LOG_TAG_VPN, "refresh proxies: $res") } catch (e: Exception) { - Logger.e(LOG_TAG_VPN, "error refreshing proxies: ${e.message}", e) + Logger.e(LOG_TAG_VPN, "err refreshing proxies: ${e.message}", e) } } @@ -802,16 +801,27 @@ class GoVpnAdapter : KoinComponent { val res = getProxies()?.getProxy(id)?.refresh() Logger.i(LOG_TAG_VPN, "refresh proxy($id): $res") } catch (e: Exception) { - Logger.e(LOG_TAG_VPN, "error refreshing proxy($id): ${e.message}", e) + Logger.e(LOG_TAG_VPN, "err refreshing proxy($id): ${e.message}", e) } } - suspend fun getProxyStats(id: String): backend.Stats? { + suspend fun getProxyStats(id: String): backend.RouterStats? { return try { val stats = getProxies()?.getProxy(id)?.router()?.stat() stats } catch (e: Exception) { - Logger.e(LOG_TAG_VPN, "error getting proxy stats($id): ${e.message}") + Logger.e(LOG_TAG_VPN, "err getting proxy stats($id): ${e.message}") + null + } + } + + fun getNetStat(): backend.NetStat? { + return try { + val stat = tunnel.stat() + Logger.i(LOG_TAG_VPN, "net stat: $stat") + stat + } catch (e: Exception) { + Logger.e(LOG_TAG_VPN, "err getting net stat: ${e.message}") null } } @@ -891,7 +901,8 @@ class GoVpnAdapter : KoinComponent { suspend fun closeTun() { try { if (tunnel.isConnected) { - Logger.i(LOG_TAG_VPN, "Tunnel disconnect") + // this is not the only place where tunnel is disconnected + // netstack can also close the tunnel on errors tunnel.disconnect() } else { Logger.i(LOG_TAG_VPN, "Tunnel already disconnected") @@ -965,6 +976,27 @@ class GoVpnAdapter : KoinComponent { } } + suspend fun getSystemDns(): String { + return try { + val sysDns = getResolver()?.get(Backend.System)?.addr ?: "" + Logger.i(LOG_TAG_VPN, "get system dns: $sysDns") + sysDns + } catch (e: Exception) { + Logger.e(LOG_TAG_VPN, "err get system dns: ${e.message}", e) + "" + } + } + + suspend fun maybeSlowdown() { + val t = persistentState.routeRethinkInRethink || VpnController.isVpnLockdown() + try { + Intra.slowdown(t) + Logger.i(LOG_TAG_VPN, "maybe slowdown? $t") + } catch (e: Exception) { + Logger.e(LOG_TAG_VPN, "err slowdown? $t, ${e.message}", e) + } + } + suspend fun setSystemDns(systemDns: List) { if (!tunnel.isConnected) { Logger.i(LOG_TAG_VPN, "Tunnel NOT connected, skip setting system-dns") @@ -994,7 +1026,7 @@ class GoVpnAdapter : KoinComponent { proto: Long ): Boolean { if (!tunnel.isConnected) { - Logger.e(LOG_TAG_VPN, "updateLink: tunFd is null, returning") + Logger.e(LOG_TAG_VPN, "updateLink: tunnel disconnected, returning") return false } Logger.i(LOG_TAG_VPN, "updateLink with fd(${tunFd.fd}) mtu: ${opts.mtu}") @@ -1122,10 +1154,11 @@ class GoVpnAdapter : KoinComponent { } fun setLogLevel(level: Int) { - // 0 - very verbose, 1 - verbose, 2 - debug, 3 - info, 4 - warn, 5 - error, 6 - stacktrace, 7 - none - // TODO: setting for console log level?, set as STACKTRACE for now - Intra.logLevel(level, Logger.LoggerType.STACKTRACE.id) - Logger.i(LOG_TAG_VPN, "set log level: $level, stacktrace") + // 0 - very verbose, 1 - verbose, 2 - debug, 3 - info, 4 - warn, 5 - error, 6 - stacktrace, 7 - user, 8 - none + // from UI, if none is selected, set the log level to 7 (user), usr will send only + // user notifications + Intra.logLevel(level, level) + Logger.i(LOG_TAG_VPN, "set go-log level: ${Logger.LoggerType.fromId(level)}") } } From 241955b97662cc40973f5887c70f234f0cec139f Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 11 Jul 2024 19:49:32 +0530 Subject: [PATCH 063/188] ui: font change for region text in dns bottom sheet --- app/src/full/res/layout/bottom_sheet_dns_log.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/full/res/layout/bottom_sheet_dns_log.xml b/app/src/full/res/layout/bottom_sheet_dns_log.xml index f1f450652..9b73eb696 100644 --- a/app/src/full/res/layout/bottom_sheet_dns_log.xml +++ b/app/src/full/res/layout/bottom_sheet_dns_log.xml @@ -231,6 +231,7 @@ android:layout_height="wrap_content" android:layout_below="@id/dns_block_blocked_desc" android:layout_centerHorizontal="true" + android:fontFamily="sans-serif-smallcaps" android:gravity="center" android:padding="5dp" android:textColor="?attr/primaryLightColorText" From 192363a11c52e451b1743daf526b2ef3228ec11d Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 11 Jul 2024 19:50:03 +0530 Subject: [PATCH 064/188] ui: new drawable for logs related settings --- app/src/full/res/layout/activity_misc_settings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/full/res/layout/activity_misc_settings.xml b/app/src/full/res/layout/activity_misc_settings.xml index 4c984c5f3..93a86ea63 100644 --- a/app/src/full/res/layout/activity_misc_settings.xml +++ b/app/src/full/res/layout/activity_misc_settings.xml @@ -173,7 +173,7 @@ android:layout_marginEnd="5dp" android:layout_marginBottom="5dp" android:padding="10dp" - android:src="@drawable/ic_logs" /> + android:src="@drawable/ic_log_level" /> + android:src="@drawable/ic_app_log" /> Date: Thu, 11 Jul 2024 19:50:50 +0530 Subject: [PATCH 065/188] viewmodel: add dependency for rinr for app connection view model --- .../full/java/com/celzero/bravedns/viewmodel/ViewModelModule.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/full/java/com/celzero/bravedns/viewmodel/ViewModelModule.kt b/app/src/full/java/com/celzero/bravedns/viewmodel/ViewModelModule.kt index fa9ea9862..c4e1b5595 100644 --- a/app/src/full/java/com/celzero/bravedns/viewmodel/ViewModelModule.kt +++ b/app/src/full/java/com/celzero/bravedns/viewmodel/ViewModelModule.kt @@ -33,7 +33,7 @@ object ViewModelModule { viewModel { AppCustomIpViewModel(get()) } viewModel { RethinkRemoteFileTagViewModel(get()) } viewModel { RethinkLocalFileTagViewModel(get()) } - viewModel { AppConnectionsViewModel(get()) } + viewModel { AppConnectionsViewModel(get(), get()) } viewModel { SummaryStatisticsViewModel(get(), get(), get()) } viewModel { DetailedStatisticsViewModel(get(), get(), get()) } viewModel { LocalBlocklistPacksMapViewModel(get()) } From bdead32a500cf3a3f123ce1e9898f2ca678560e9 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 11 Jul 2024 19:56:53 +0530 Subject: [PATCH 066/188] tunnel: multiple improvements in tunnel make vpnAdapter volatile serializer for adapter creation and updates channel to perform wg handshakes route v4 in v6 only networks protect the ping sockets livedata for region mv unbind operations to onRevoke --- .../bravedns/service/BraveVPNService.kt | 550 ++++++++++++------ 1 file changed, 368 insertions(+), 182 deletions(-) diff --git a/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt b/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt index d4d09e826..fdf0ea1dc 100644 --- a/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt +++ b/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt @@ -17,6 +17,7 @@ package com.celzero.bravedns.service import Logger +import Logger.LOG_BATCH_LOGGER import Logger.LOG_GO_LOGGER import Logger.LOG_TAG_VPN import android.app.ActivityManager @@ -57,11 +58,12 @@ import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat import androidx.core.app.ServiceCompat import androidx.core.content.ContextCompat +import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer import backend.Backend import backend.RDNS -import backend.Stats +import backend.RouterStats import com.celzero.bravedns.R import com.celzero.bravedns.RethinkDnsApplication.Companion.DEBUG import com.celzero.bravedns.data.AppConfig @@ -69,6 +71,7 @@ import com.celzero.bravedns.data.ConnTrackerMetaData import com.celzero.bravedns.data.ConnectionSummary import com.celzero.bravedns.database.AppInfo import com.celzero.bravedns.database.ConnectionTracker +import com.celzero.bravedns.database.ConsoleLog import com.celzero.bravedns.database.RefreshDatabase import com.celzero.bravedns.net.go.GoVpnAdapter import com.celzero.bravedns.net.manager.ConnectionTracer @@ -85,6 +88,7 @@ import com.celzero.bravedns.util.Constants.Companion.NOTIF_INTENT_EXTRA_ACCESSIB import com.celzero.bravedns.util.Constants.Companion.NOTIF_INTENT_EXTRA_ACCESSIBILITY_VALUE import com.celzero.bravedns.util.Constants.Companion.PRIMARY_USER import com.celzero.bravedns.util.Constants.Companion.UID_EVERYBODY +import com.celzero.bravedns.util.Daemons import com.celzero.bravedns.util.IPUtil import com.celzero.bravedns.util.InternetProtocol import com.celzero.bravedns.util.KnownPorts @@ -106,9 +110,11 @@ import com.google.common.collect.Sets import inet.ipaddr.HostName import inet.ipaddr.IPAddressString import intra.Bridge +import intra.Mark import intra.SocketSummary import java.io.IOException import java.net.InetAddress +import java.net.Socket import java.net.SocketException import java.net.UnknownHostException import java.util.Collections @@ -116,15 +122,20 @@ import java.util.Locale import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.TimeUnit import java.util.concurrent.atomic.AtomicBoolean +import kotlin.coroutines.cancellation.CancellationException import kotlin.math.abs import kotlin.math.min import kotlin.random.Random +import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.MainScope import kotlinx.coroutines.async import kotlinx.coroutines.cancel +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.channels.consumeEach import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext @@ -136,8 +147,17 @@ class BraveVPNService : VpnService(), ConnectionMonitor.NetworkListener, Bridge, OnSharedPreferenceChangeListener { private var connectionMonitor: ConnectionMonitor = ConnectionMonitor(this) + + // multiple coroutines call both signalStopService and makeOrUpdateVpnAdapter and so + // set and unset this variable on the serializer thread + @Volatile private var vpnAdapter: GoVpnAdapter? = null - private var isVpnStarted = false // always accessed on the main thread + + // used mostly for service to adapter creation and updates + private var serializer: CoroutineDispatcher = Daemons.make("vpnser") + + // channel to perform wg handshakes + private val wgHandshakeChannel = Channel(Channel.CONFLATED) companion object { const val SERVICE_ID = 1 // Only has to be unique within this app. @@ -171,6 +191,9 @@ class BraveVPNService : // TODO: add routes as normal but do not send fd to netstack // repopulateTrackedNetworks also fails open see isAnyNwValidated const val FAIL_OPEN_ON_NO_NETWORK = true + + // route v4 in v6 only networks? + const val ROUTE4IN6 = true } // handshake expiry time for proxy connections @@ -178,7 +201,7 @@ class BraveVPNService : private val wgHandshakeTimeout = TimeUnit.SECONDS.toMillis(160L) private val checkpointInterval = TimeUnit.MINUTES.toMillis(1L) - private var isLockDownPrevious: Boolean = false + private var isLockDownPrevious = AtomicBoolean(false) private val vpnScope = MainScope() private lateinit var connTracer: ConnectionTracer @@ -215,7 +238,7 @@ class BraveVPNService : private var trackedCids = Collections.newSetFromMap(ConcurrentHashMap()) // store proxyids and their handshake times, refresh proxy when handshake time is expired - private val wgHandShakeCheckpoints: ConcurrentHashMap = ConcurrentHashMap() + private val wgHandShakeCheckpoints: HashMap = HashMap() // data class to store the connection summary data class CidKey(val cid: String, val uid: Int) @@ -231,6 +254,9 @@ class BraveVPNService : private var accessibilityListener: AccessibilityManager.AccessibilityStateChangeListener? = null + // live-data to store the region received from onResponse + val regionLiveData: MutableLiveData = MutableLiveData() + data class OverlayNetworks( val has4: Boolean = false, val has6: Boolean = false, @@ -254,12 +280,27 @@ class BraveVPNService : APP_ERROR } + data class ProxyOperationData(val proxyId: String, val action: ProxyOperation) + + enum class ProxyOperation { + ADD, + REMOVE, + CLEAR, + REFRESH + } + private fun logd(msg: String) { Logger.d(LOG_TAG_VPN, msg) } override fun bind4(who: String, addrPort: String, fid: Long) { - return bindAny(who, addrPort, fid, underlyingNetworks?.ipv4Net ?: emptyList()) + var v4Net = underlyingNetworks?.ipv4Net + val isAuto = InternetProtocol.isAuto(persistentState.internetProtocolType) + if (ROUTE4IN6 && isAuto && v4Net.isNullOrEmpty()) { + v4Net = underlyingNetworks?.ipv6Net + } + + return bindAny(who, addrPort, fid, v4Net ?: emptyList()) } override fun bind6(who: String, addrPort: String, fid: Long) { @@ -300,7 +341,8 @@ class BraveVPNService : // network with zero addresses if ( (destIp.isZero && who.startsWith(ProxyManager.ID_WG_BASE)) || - destIp.isZero || destIp.isLoopback + destIp.isZero || + destIp.isLoopback ) { logd("bind: invalid destIp: $destIp, who: $who, $addrPort") return @@ -347,6 +389,15 @@ class BraveVPNService : } } + suspend fun probeIp(ip: String): ConnectionMonitor.ProbeResult? { + return connectionMonitor.probeIp(ip) + } + + fun protectSocket(socket: Socket) { + this.protect(socket) + Logger.v(LOG_TAG_VPN, "socket protected") + } + override fun protect(who: String?, fd: Long) { val rinr = persistentState.routeRethinkInRethink logd("protect: $who, $fd, rinr? $rinr") @@ -358,7 +409,7 @@ class BraveVPNService : } private suspend fun getUid( - _uid: Long, + _uid: Int, protocol: Int, srcIp: String, srcPort: Int, @@ -369,7 +420,7 @@ class BraveVPNService : ioAsync("getUidQ") { connTracer.getUidQ(protocol, srcIp, srcPort, dstIp, dstPort) } .await() } else { - _uid.toInt() // uid must have been retrieved from procfs by the caller + _uid // uid must have been retrieved from procfs by the caller } } @@ -1009,6 +1060,7 @@ class BraveVPNService : // underlying networks is set to null, which prompts Android to set it to whatever is // the current active network. Later, ConnectionMonitor#onVpnStarted, depending on user // chosen preferences, sets appropriate underlying network/s. + Logger.d(LOG_TAG_VPN, "builder: useActive, set underlying networks to null") builder.setUnderlyingNetworks(null) } else { // add ipv4 and ipv6 networks to the tunnel @@ -1016,11 +1068,14 @@ class BraveVPNService : val ipv6 = curnet.ipv6Net.map { it.network } val allNetworks = ipv4.plus(ipv6) if (allNetworks.isNotEmpty()) { + Logger.d(LOG_TAG_VPN, "builder: underlying networks: ${allNetworks.size}") builder.setUnderlyingNetworks(allNetworks.toTypedArray()) } else { if (FAIL_OPEN_ON_NO_NETWORK) { + Logger.d(LOG_TAG_VPN, "builder: no networks, set underlying networks to null") builder.setUnderlyingNetworks(null) } else { + Logger.d(LOG_TAG_VPN, "builder: no networks, set underlying networks to empty") builder.setUnderlyingNetworks(emptyArray()) } } @@ -1158,7 +1213,11 @@ class BraveVPNService : connTracer = ConnectionTracer(this) VpnController.onVpnCreated(this) - io("loggers") { netLogTracker.restart(vpnScope) } + io("nlt") { + Log.d(LOG_BATCH_LOGGER, "vpn: restart $vpnScope") + netLogTracker.restart(vpnScope) + } + createWgHandshakeChannel() notificationManager = this.getSystemService(NOTIFICATION_SERVICE) as NotificationManager activityManager = this.getSystemService(ACTIVITY_SERVICE) as ActivityManager @@ -1282,14 +1341,17 @@ class BraveVPNService : // 3. No action button. val isAppLockEnabled = persistentState.biometricAuth && !isAppRunningOnTv() // do not show notification action when app lock is enabled - val notifActionType = if (isAppLockEnabled) { - NotificationActionType.NONE - } else { - NotificationActionType.getNotificationActionType( - persistentState.notificationActionType - ) - } - logd("notification action type: ${persistentState.notificationActionType}, $notifActionType") + val notifActionType = + if (isAppLockEnabled) { + NotificationActionType.NONE + } else { + NotificationActionType.getNotificationActionType( + persistentState.notificationActionType + ) + } + logd( + "notification action type: ${persistentState.notificationActionType}, $notifActionType" + ) when (notifActionType) { NotificationActionType.PAUSE_STOP -> { @@ -1460,17 +1522,13 @@ class BraveVPNService : // see restartVpn and updateTun which expect this to be the case persistentState.setVpnEnabled(true) - var isNewVpn = false - - if (!isVpnStarted) { - Logger.i(LOG_TAG_VPN, "new vpn") - isVpnStarted = true - isNewVpn = true - connectionMonitor.onVpnStart(this) - } + val isNewVpn = connectionMonitor.onVpnStart(this) - if (isVpnStarted) { + if (isNewVpn) { + // clear the underlying networks, so that the new vpn can be created with the + // current active network. underlyingNetworks = null + Logger.i(LOG_TAG_VPN, "new vpn") } val mtu = mtu() @@ -1482,7 +1540,7 @@ class BraveVPNService : mtu ) - Logger.i(LOG_TAG_VPN, "start-foreground with opts $opts (for new-vpn? $isNewVpn)") + Logger.i(LOG_TAG_VPN, "start-fg with opts $opts (for new-vpn? $isNewVpn)") if (!isNewVpn) { io("tunUpdate") { // may call signalStopService(userInitiated=false) if go-vpn-adapter is missing @@ -1496,7 +1554,7 @@ class BraveVPNService : // refresh should happen before restartVpn, otherwise the new vpn will not // have app, ip, domain rules. See RefreshDatabase#refresh rdb.refresh(RefreshDatabase.ACTION_REFRESH_AUTO) { - restartVpn(opts, Networks(null, overlayNetworks), why = "startVpn") + restartVpn(this, opts, Networks(null, overlayNetworks), why = "startVpn") // call this *after* a new vpn is created #512 uiCtx("observers") { observeChanges() } } @@ -1790,6 +1848,7 @@ class BraveVPNService : // restart vpn to allow/disallow rethink traffic in rethink io("routeRethinkInRethink") { restartVpnWithNewAppConfig(reason = "routeRethinkInRethink") + vpnAdapter?.maybeSlowdown() } } @@ -1915,7 +1974,9 @@ class BraveVPNService : io("dnsStampUpdate") { vpnAdapter?.setRDNSStamp() } } - private suspend fun notifyConnectionMonitor() { + // invoked on pref / probe-ip changes, so that the connection monitor can + // re-initiate the connectivity checks + suspend fun notifyConnectionMonitor() { connectionMonitor.onUserPreferenceChanged() } @@ -1925,21 +1986,22 @@ class BraveVPNService : fun signalStopService(reason: String, userInitiated: Boolean = true) { if (!userInitiated) notifyUserOnVpnFailure() + io(reason) { stopVpnAdapter() } stopSelf() Logger.i(LOG_TAG_VPN, "stopped vpn adapter & service: $reason, $userInitiated") } - private fun stopVpnAdapter() { - io("stopVpn") { + private suspend fun stopVpnAdapter() = + withContext(CoroutineName("stopVpn") + serializer) { if (vpnAdapter == null) { Logger.i(LOG_TAG_VPN, "vpn adapter already stopped") - return@io + return@withContext } + vpnAdapter?.closeTun() vpnAdapter = null Logger.i(LOG_TAG_VPN, "stop vpn adapter") } - } private suspend fun restartVpnWithNewAppConfig( underlyingNws: ConnectionMonitor.UnderlyingNetworks? = underlyingNetworks, @@ -1949,6 +2011,7 @@ class BraveVPNService : logd("restart vpn with new app config") val nws = Networks(underlyingNws, overlayNws) restartVpn( + this, appConfig.newTunnelOptions( this, getFakeDns(), @@ -1981,40 +2044,61 @@ class BraveVPNService : vpnAdapter?.setTunMode(tunnelOptions) } - private suspend fun restartVpn(opts: AppConfig.TunnelOptions, networks: Networks, why: String) { - if (!persistentState.getVpnEnabled()) { - // when persistent-state "thinks" vpn is disabled, stop the service, especially when - // we could be here via onStartCommand -> isNewVpn -> restartVpn while both, - // vpn-service & conn-monitor exists & vpn-enabled state goes out of sync - logAndToastIfNeeded( - "$why, stop-vpn(restartVpn), tracking vpn is out of sync", - Log.ERROR + private suspend fun restartVpn( + ctx: Context, + opts: AppConfig.TunnelOptions, + networks: Networks, + why: String + ) = + withContext(CoroutineName(why) + serializer) { + if (!persistentState.getVpnEnabled()) { + // when persistent-state "thinks" vpn is disabled, stop the service, especially when + // we could be here via onStartCommand -> isNewVpn -> restartVpn while both, + // vpn-service & conn-monitor exists & vpn-enabled state goes out of sync + logAndToastIfNeeded( + "$why, stop-vpn(restartVpn), tracking vpn is out of sync", + Log.ERROR + ) + io("outOfSyncRestart") { + signalStopService("outOfSyncRestart", userInitiated = false) + } + return@withContext + } + Logger.i( + LOG_TAG_VPN, + "---------------------------RESTART-INIT----------------------------" + ) + // attempt seamless hand-off as described in VpnService.Builder.establish() docs + val tunFd = establishVpn(networks) + if (tunFd == null) { + logAndToastIfNeeded("$why, cannot restart-vpn, no tun-fd", Log.ERROR) + io("noTunRestart1") { signalStopService("noTunRestart1", userInitiated = false) } + return@withContext + } + + val ok = + makeOrUpdateVpnAdapter( + ctx, + tunFd, + opts, + vpnProtos + ) // vpnProtos set in establishVpn() + if (!ok) { + logAndToastIfNeeded("$why, cannot restart-vpn, no vpn-adapter", Log.ERROR) + io("noTunnelRestart2") { signalStopService("noTunRestart2", userInitiated = false) } + return@withContext + } else { + logAndToastIfNeeded("$why, vpn restarted", Log.INFO) + } + Logger.i( + LOG_TAG_VPN, + "---------------------------RESTART-OK----------------------------" ) - io("outOfSyncRestart") { signalStopService("outOfSyncRestart", userInitiated = false) } - return - } - - // attempt seamless hand-off as described in VpnService.Builder.establish() docs - val tunFd = establishVpn(networks) - if (tunFd == null) { - logAndToastIfNeeded("$why, cannot restart-vpn, no tun-fd", Log.ERROR) - io("noTunRestart") { signalStopService("noTunRestart1", userInitiated = false) } - return - } - val ok = makeOrUpdateVpnAdapter(tunFd, opts, vpnProtos) // vpnProtos set in establishVpn() - if (!ok) { - logAndToastIfNeeded("$why, cannot restart-vpn, no vpn-adapter", Log.ERROR) - io("noTunnelRestart") { signalStopService("noTunRestart2", userInitiated = false) } - return - } else { - logAndToastIfNeeded("$why, vpn restarted", Log.INFO) + notifyConnectionStateChangeIfNeeded() + informVpnControllerForProtoChange(vpnProtos) } - notifyConnectionStateChangeIfNeeded() - informVpnControllerForProtoChange(vpnProtos) - } - private suspend fun logAndToastIfNeeded(msg: String, logLevel: Int = Log.WARN) { when (logLevel) { Log.WARN -> Logger.w(LOG_TAG_VPN, msg) @@ -2047,40 +2131,42 @@ class BraveVPNService : } private suspend fun makeOrUpdateVpnAdapter( + ctx: Context, tunFd: ParcelFileDescriptor, opts: AppConfig.TunnelOptions, p: Pair - ): Boolean { - val ok = true - val noTun = false // should eventually call signalStopService(userInitiated=false) - val protos = InternetProtocol.byProtos(p.first, p.second).value() - try { - if (vpnAdapter != null) { - Logger.i(LOG_TAG_VPN, "vpn-adapter exists, use it") - // in case, if vpn-adapter exists, update the existing vpn-adapter - if (vpnAdapter?.updateLinkAndRoutes(tunFd, opts, protos) == false) { - Logger.e(LOG_TAG_VPN, "err update vpn-adapter") - return noTun + ): Boolean = + withContext(CoroutineName("makeVpn") + serializer) { + val ok = true + val noTun = false // should eventually call signalStopService(userInitiated=false) + val protos = InternetProtocol.byProtos(p.first, p.second).value() + try { + if (vpnAdapter == null) { + // create a new vpn adapter + vpnAdapter = GoVpnAdapter(ctx, vpnScope, tunFd, opts) // may throw + GoVpnAdapter.setLogLevel(persistentState.goLoggerLevel.toInt()) + vpnAdapter!!.initResolverProxiesPcap(opts) + return@withContext ok + } else { + Logger.i(LOG_TAG_VPN, "vpn-adapter exists, use it") + // in case, if vpn-adapter exists, update the existing vpn-adapter + if (vpnAdapter?.updateLinkAndRoutes(tunFd, opts, protos) == false) { + Logger.e(LOG_TAG_VPN, "err update vpn-adapter") + return@withContext noTun + } + return@withContext ok + } + } catch (e: Exception) { + Logger.e(LOG_TAG_VPN, "err new vpn-adapter: ${e.message}", e) + return@withContext noTun + } finally { + try { // close the tunFd as GoVpnAdapter has its own copy + tunFd.close() + } catch (ignored: IOException) { + Logger.e(LOG_TAG_VPN, "err closing tunFd: ${ignored.message}", ignored) } - return ok - } else { - // create a new vpn adapter - vpnAdapter = GoVpnAdapter(this, vpnScope, tunFd, opts) // may throw - GoVpnAdapter.setLogLevel(persistentState.goLoggerLevel.toInt()) - vpnAdapter!!.initResolverProxiesPcap(opts) - return ok - } - } catch (e: Exception) { - Logger.e(LOG_TAG_VPN, "err new vpn-adapter: ${e.message}", e) - return noTun - } finally { - try { // close the tunFd as GoVpnAdapter has its own copy - tunFd.close() - } catch (ignored: IOException) { - Logger.e(LOG_TAG_VPN, "err closing tunFd: ${ignored.message}", ignored) } } - } // TODO: #294 - Figure out a way to show users that the device is offline instead of status as // failing. @@ -2282,6 +2368,9 @@ class BraveVPNService : // get dns servers from the first network or active network val active = connectivityManager.activeNetwork val lp = connectivityManager.getLinkProperties(active) + // here dnsServers are validated with underlyingNetworks, so there may be a case + // where v6 address is added when v6 network is not available + // so, dnsServers will have both v4 and v6 addresses val dnsServers = lp?.dnsServers if (dnsServers.isNullOrEmpty()) { @@ -2352,16 +2441,24 @@ class BraveVPNService : private fun handleVpnLockdownStateAsync() { if (!syncLockdownState()) return + Logger.i(LOG_TAG_VPN, "vpn lockdown mode change, restarting") - io("lockdownSync") { restartVpnWithNewAppConfig(reason = "lockdownSync") } + io("lockdownSync") { + restartVpnWithNewAppConfig(reason = "lockdownSync") + vpnAdapter?.maybeSlowdown() + } } private fun syncLockdownState(): Boolean { if (!isAtleastQ()) return false - val ret = isLockdownEnabled != isLockDownPrevious - isLockDownPrevious = this.isLockdownEnabled - return ret + // cannot set the lockdown status while the vpn is being created, it will return false + // until the vpn is created. so the sync will be done after the vpn is created + // when the first flow call is made. + val prev = isLockDownPrevious.get() + if (isLockdownEnabled == prev) return false + + return isLockDownPrevious.compareAndSet(prev, isLockdownEnabled) } private fun notifyUserOnVpnFailure() { @@ -2415,7 +2512,6 @@ class BraveVPNService : stopPauseTimer() // reset the underlying networks underlyingNetworks = null - isVpnStarted = false // reset the vpn state unobserveOrbotStartStatus() unobserveAppInfos() @@ -2426,8 +2522,15 @@ class BraveVPNService : connectionMonitor.onVpnStop() VpnController.onVpnDestroyed() try { + // cancel the job which cancels all coroutines started in this scope + // cancel the jobs before cancelling the scope + vpnScope.coroutineContext[Job]?.cancel("vpnDestroy") vpnScope.cancel("vpnDestroy") } catch (ignored: IllegalStateException) { + } catch ( + ignored: CancellationException + ) { + } catch (ignored: Exception) { } Logger.w(LOG_TAG_VPN, "Destroying VPN service") @@ -2506,6 +2609,14 @@ class BraveVPNService : var has6 = route6(n) var has4 = route4(n) + val isAuto = InternetProtocol.isAuto(persistentState.internetProtocolType) + // in auto mode, assume v4 route is available if only v6 route is available, which is true + // for scenarios like 464Xlat and other 4to6 translation mechanisms + if (ROUTE4IN6 && isAuto && (has6 && !has4)) { + Logger.w(LOG_TAG_VPN, "Adding v4 route in v6-only network") + has4 = true + } + if (!has4 && !has6 && !n.overlayNws.failOpen) { // When overlay networks has v6 routes but active network has v4 routes // both has4 and has6 will be false and fail-open may open up BOTH routes @@ -3048,12 +3159,30 @@ class BraveVPNService : logd("onResponse: $summary") netLogTracker.processDnsLog(summary) + setRegionLiveDataIfRequired(summary) + } + + private fun setRegionLiveDataIfRequired(summary: backend.DNSSummary) { + if (summary.region.isNullOrEmpty()) { + return + } + + val region = summary.region + val regionLiveData = regionLiveData + if (regionLiveData.value != region) { + regionLiveData.postValue(region) + } + } + + fun getRegionLiveData(): LiveData { + return regionLiveData } override fun onProxiesStopped() { // clear the proxy handshake times logd("onProxiesStopped; clear the handshake times") - wgHandShakeCheckpoints.clear() + val action = ProxyOperationData("-1", ProxyOperation.CLEAR) + handleProxyActions(action) WireguardManager.clearCatchAllCache() } @@ -3062,7 +3191,8 @@ class BraveVPNService : // only wireguard proxies are considered for overlay network return } - wgHandShakeCheckpoints[id] = elapsedRealtime() + val action = ProxyOperationData(id, ProxyOperation.ADD) + handleProxyActions(action) // new proxy added, refresh overlay network pair io("onProxyAdded") { val nw: OverlayNetworks? = vpnAdapter?.getActiveProxiesIpAndMtu() @@ -3076,7 +3206,8 @@ class BraveVPNService : // only wireguard proxies are considered for overlay network return } - wgHandShakeCheckpoints.remove(id) + val action = ProxyOperationData(id, ProxyOperation.REMOVE) + handleProxyActions(action) WireguardManager.clearCatchAllCacheForApp(id) // proxy removed, refresh overlay network pair io("onProxyRemoved") { @@ -3116,22 +3247,19 @@ class BraveVPNService : } override fun log(level: Int, msg: String) { + if (msg.isEmpty()) return + val l = Logger.LoggerType.fromId(level) - writeConsoleLog(msg) if (l.stacktrace()) { - Logger.crash(LOG_GO_LOGGER, msg) // log the stack trace + Logger.crash(LOG_GO_LOGGER, msg) // log the stack trace and write to in-mem db EnhancedBugReport.writeLogsToFile(this, msg) } else if (l.user()) { showNwEngineNotification(msg) } else { - // no-op + Logger.goLog(msg, l) } } - fun writeConsoleLog(msg: String) { - netLogTracker.writeConsoleLog(msg) - } - private fun showNwEngineNotification(msg: String) { if (msg.isEmpty()) { return @@ -3144,14 +3272,14 @@ class BraveVPNService : PendingIntent.FLAG_UPDATE_CURRENT, mutable = false ) - val builder = NotificationCompat.Builder(this, WARNING_CHANNEL_ID) - .setSmallIcon(R.drawable.ic_notification_icon) - .setContentTitle(msg) - .setContentIntent(pendingIntent) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setAutoCancel(true) - builder.color = - ContextCompat.getColor(this, getAccentColor(persistentState.theme)) + val builder = + NotificationCompat.Builder(this, WARNING_CHANNEL_ID) + .setSmallIcon(R.drawable.ic_notification_icon) + .setContentTitle(msg) + .setContentIntent(pendingIntent) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setAutoCancel(true) + builder.color = ContextCompat.getColor(this, getAccentColor(persistentState.theme)) notificationManager.notify(NW_ENGINE_NOTIFICATION_ID, builder.build()) } @@ -3203,20 +3331,23 @@ class BraveVPNService : override fun flow( protocol: Int, - _uid: Long, - dup: Boolean, + _uid: Int, src: String, dest: String, realIps: String, d: String, possibleDomains: String, blocklists: String - ): intra.Mark = runBlocking { + ): Mark = runBlocking { // runBlocking blocks the current thread until all coroutines within it are complete // an call a suspending function from a non-suspending context and obtain the result. - logd("flow: $_uid, $dup, $src, $dest, $realIps, $d, $blocklists") + logd("flow: $_uid, $src, $dest, $realIps, $d, $blocklists") handleVpnLockdownStateAsync() + // in case of double loopback, all traffic will be part of rinr instead of rethink's own + // traffic, flip the doubleLoopback flag to true if we need that behavior + val doubleLoopback = false + val first = HostName(src) val second = HostName(dest) @@ -3273,8 +3404,6 @@ class BraveVPNService : val trapVpnDns = isDns(dstPort) && isVpnDns(dstIp) val trapVpnPrivateDns = isVpnDns(dstIp) && isPrivateDns(dstPort) - // app is considered as spl when it is selected to forward dns proxy, socks5 or http proxy - val isSplApp = isSpecialApp(uid) // always block, since the vpn tunnel doesn't serve dns-over-tls if (trapVpnPrivateDns) { @@ -3284,11 +3413,14 @@ class BraveVPNService : return@runBlocking persistAndConstructFlowResponse(cm, Backend.Block, connId, uid) } + // app is considered as spl when it is selected to forward dns proxy, socks5 or http proxy + val isSplApp = isSpecialApp(uid) + val isRethink = uid == rethinkUid if (isRethink) { // case when uid is rethink, return Ipn.Base logd( - "flow: Ipn.Exit for rethink, $uid, $dup, $packageName, $srcIp, $srcPort, $realDestIp, $dstPort, $possibleDomains" + "flow: Ipn.Exit for rethink, $uid, $packageName, $srcIp, $srcPort, $realDestIp, $dstPort, $possibleDomains" ) if (cm.query.isNullOrEmpty()) { // possible domains only used for logging purposes, it may be available if @@ -3314,17 +3446,7 @@ class BraveVPNService : val key = CidKey(cm.connId, uid) trackedCids.add(key) - // TODO: set dup as true for now (v055f), need to handle dup properly in future - val duplicate = dup || true - // if the connection is Rethink's uid and if the dup is false, then the connections - // are rethink's own connections, so add it in network log as well - if (!duplicate) { - // no need to consider return value as the function is called only for logging - persistAndConstructFlowResponse(cm, proxy, connId, uid) - } - // make the cm obj to null so that the db write will not happen - val c = if (duplicate) cm else null - return@runBlocking persistAndConstructFlowResponse(c, proxy, connId, uid, isRethink) + return@runBlocking persistAndConstructFlowResponse(cm, proxy, connId, uid, isRethink) } if (trapVpnDns) { @@ -3345,7 +3467,7 @@ class BraveVPNService : val key = CidKey(cm.connId, uid) trackedCids.add(key) - return@runBlocking determineProxyDetails(cm, isSplApp) + return@runBlocking determineProxyDetails(cm, doubleLoopback) } private suspend fun isSpecialApp(uid: Int): Boolean { @@ -3406,13 +3528,13 @@ class BraveVPNService : private suspend fun determineProxyDetails( connTracker: ConnTrackerMetaData, - isSplApp: Boolean + doubleLoopback: Boolean ): intra.Mark { val baseOrExit = - if (isSplApp) { - Backend.Exit - } else { + if (doubleLoopback) { Backend.Base + } else { + Backend.Exit } val connId = connTracker.connId val uid = connTracker.uid @@ -3427,42 +3549,41 @@ class BraveVPNService : if (oneWgId != null && oneWgId != WireguardManager.INVALID_CONF_ID) { val proxyId = "${ProxyManager.ID_WG_BASE}${oneWgId}" // regardless of whether this proxyId exists in go, use it to avoid leaks + val ipSupported = isIpSupportedByWireGuardId(proxyId, connTracker.destIP) val canRoute = canRouteIp(proxyId, connTracker.destIP, true) - return if (canRoute) { - handleProxyHandshake(proxyId) + return if (ipSupported && canRoute) { + val actions = ProxyOperationData(proxyId, ProxyOperation.REFRESH) + handleProxyActions(actions) logd("flow: one-wg is enabled, returning $proxyId, $connId, $uid") persistAndConstructFlowResponse(connTracker, proxyId, connId, uid) } else { // in some configurations the allowed ips will not be 0.0.0.0/0, so the connection // will be dropped, in those cases, return base (connection will be forwarded to // base proxy) - logd("flow: one-wg is enabled, but no route; ret:Ipn.Base, $connId, $uid") + logd("flow: one-wg is enabled, but no route/ip; ret:Ipn.Base, $connId, $uid") persistAndConstructFlowResponse(connTracker, baseOrExit, connId, uid) } } - val wgConfig = WireguardManager.getConfigIdForApp(uid, connTracker.destIP) // also accounts for catch-all + val wgConfig = + WireguardManager.getConfigIdForApp( + uid, + connTracker.destIP + ) // also accounts for catch-all if (wgConfig != null && wgConfig.id != WireguardManager.INVALID_CONF_ID) { val proxyId = "${ProxyManager.ID_WG_BASE}${wgConfig.id}" - // even if inactive, route connections to wg if lockdown/catch-all is enabled to - // avoid leaks - if (wgConfig.isActive || wgConfig.isLockdown || wgConfig.isCatchAll) { - // if lockdown is enabled, canRoute checks peer configuration and if it returns - // "false", then the connection will be sent to base and not dropped - // if lockdown is disabled, then canRoute returns default (true) which - // will have the effect of blocking all connections - // ie, if lockdown is enabled, split-tunneling happens as expected but if - // lockdown is disabled, it has the effect of blocking all connections - val canRoute = canRouteIp(proxyId, connTracker.destIP, true) - logd("flow: wg is active/lockdown/catch-all; $proxyId, $connId, $uid; canRoute? $canRoute") - return if (canRoute) { - handleProxyHandshake(proxyId) - persistAndConstructFlowResponse(connTracker, proxyId, connId, uid) - } else { - persistAndConstructFlowResponse(connTracker, baseOrExit, connId, uid) - } + // TODO: WgMgr takes care of giving the correct proxyId, check for canRouteIp to + // trigger the handshake if needed + val canRoute = canRouteIp(proxyId, connTracker.destIP, true) + logd( + "flow: wg is active/lockdown/catch-all; $proxyId, $connId, $uid; canRoute? $canRoute" + ) + return if (canRoute) { + val actions = ProxyOperationData(proxyId, ProxyOperation.REFRESH) + handleProxyActions(actions) + persistAndConstructFlowResponse(connTracker, proxyId, connId, uid) } else { - // fall-through, no lockdown/catch-all/active wg found, so proceed with other checks + persistAndConstructFlowResponse(connTracker, baseOrExit, connId, uid) } } @@ -3579,6 +3700,51 @@ class BraveVPNService : return persistAndConstructFlowResponse(connTracker, baseOrExit, connId, uid) } + private suspend fun isIpSupportedByWireGuardId(id: String, ip: String): Boolean { + val ipVersion = IPAddressString(ip).toAddress().ipVersion + val supportedVersion = VpnController.getSupportedIpVersion(id) + logd( + "isSupportedIpVersion: $id? ipVersion: $ipVersion, supportedVersion: $supportedVersion" + ) + if (ipVersion.isIPv4 && supportedVersion.first) { + return true + } + if (ipVersion.isIPv6 && supportedVersion.second) { + return true + } + return false + } + + private fun createWgHandshakeChannel() { + // no need to cancel the channel as vpnScope is cancelled when vpn is stopped + vpnScope.launch { + wgHandshakeChannel.consumeEach { action -> + when (action.action) { + ProxyOperation.ADD -> { + wgHandShakeCheckpoints[action.proxyId] = elapsedRealtime() + } + + ProxyOperation.REMOVE -> { + wgHandShakeCheckpoints.remove(action.proxyId) + } + + ProxyOperation.REFRESH -> handleProxyHandshake(action.proxyId) + ProxyOperation.CLEAR -> { + wgHandShakeCheckpoints.clear() + } + } + } + } + } + + private fun handleProxyActions(action: ProxyOperationData) { + try { + vpnScope.launch { wgHandshakeChannel.send(action) } + } catch (e: Exception) { + Logger.e(LOG_TAG_VPN, "performProxyHandshake: ${e.message}") + } + } + private suspend fun handleProxyHandshake(id: String) { if (!id.startsWith(ProxyManager.ID_WG_BASE)) { // only wireguard proxies are considered for handshakes @@ -3600,25 +3766,26 @@ class BraveVPNService : val realtime = elapsedRealtime() val cpInterval = realtime - latestCheckpoint val cpIntervalSecs = TimeUnit.MILLISECONDS.toSeconds(cpInterval) - if (cpInterval < this.checkpointInterval) { + if (cpInterval < checkpointInterval) { logd("flow: skip refresh for $id, within interval: $cpIntervalSecs") return } wgHandShakeCheckpoints[id] = realtime val lastHandShake = stats.lastOK - val mustRefresh = if (lastHandShake <= 0) { - Logger.w(LOG_TAG_VPN, "flow: force refresh, handshake never done for $id") - true // always refresh if handshake never done - } else { - logd("flow: handshake check for $id, $lastHandShake, interval: $cpIntervalSecs") - val currTimeMs = System.currentTimeMillis() - val durationMs = currTimeMs - lastHandShake - val durationSecs = TimeUnit.MILLISECONDS.toSeconds(durationMs) - val ref = durationMs > wgHandshakeTimeout - Logger.i(LOG_TAG_VPN, "flow: refresh $id after $durationSecs? $ref") - // if the last handshake is older than the timeout, refresh the proxy - ref - } + val mustRefresh = + if (lastHandShake <= 0) { + Logger.w(LOG_TAG_VPN, "flow: force refresh, handshake never done for $id") + true // always refresh if handshake never done + } else { + logd("flow: handshake check for $id, $lastHandShake, interval: $cpIntervalSecs") + val currTimeMs = System.currentTimeMillis() + val durationMs = currTimeMs - lastHandShake + val durationSecs = TimeUnit.MILLISECONDS.toSeconds(durationMs) + val ref = durationMs > wgHandshakeTimeout + Logger.i(LOG_TAG_VPN, "flow: refresh $id after $durationSecs? $ref") + // if the last handshake is older than the timeout, refresh the proxy + ref + } if (mustRefresh) { io("proxyHandshake") { vpnAdapter?.refreshProxy(id) } } @@ -3772,7 +3939,7 @@ class BraveVPNService : } } - suspend fun getProxyStats(id: String): Stats? { + suspend fun getProxyStats(id: String): RouterStats? { return if (vpnAdapter != null) { vpnAdapter?.getProxyStats(id) } else { @@ -3816,6 +3983,7 @@ class BraveVPNService : // show notification to user, that the app is consuming more memory showMemoryNotification() } + io("onLowMem") { vpnAdapter?.onLowMemory() } } private fun showMemoryNotification() { @@ -3827,24 +3995,42 @@ class BraveVPNService : mutable = false ) - val builder = NotificationCompat.Builder(this, WARNING_CHANNEL_ID) - .setContentTitle(getString(R.string.memory_notification_text)) - .setSmallIcon(R.drawable.ic_notification_icon) - .setPriority(NotificationCompat.PRIORITY_DEFAULT) - .setContentIntent(pendingIntent) - .setAutoCancel(true) + val builder = + NotificationCompat.Builder(this, WARNING_CHANNEL_ID) + .setContentTitle(getString(R.string.memory_notification_text)) + .setSmallIcon(R.drawable.ic_notification_icon) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setContentIntent(pendingIntent) + .setAutoCancel(true) builder.color = ContextCompat.getColor(this, getAccentColor(persistentState.theme)) val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager notificationManager.notify(MEMORY_NOTIFICATION_ID, builder.build()) } - override fun onUnbind(intent: Intent?): Boolean { + override fun onRevoke() { + Logger.i(LOG_TAG_VPN, "onRevoke, stop vpn adapter") + signalStopService("revoked", false) + } + + suspend fun getSystemDns(): String { + return vpnAdapter?.getSystemDns() ?: "" + } + + fun getNetStat(): backend.NetStat? { + return vpnAdapter?.getNetStat() + } + + fun writeConsoleLog(log: ConsoleLog) { + netLogTracker.writeConsoleLog(log) + } + + /*override fun onUnbind(intent: Intent?): Boolean { Logger.w(LOG_TAG_VPN, "onUnbind, stop vpn adapter") // onUnbind is called when the vpn is disconnected by signalStopService or if // some other vpn service is started by the user, so stop the vpn adapter in onUnbind which // will close tunFd which is a prerequisite for onDestroy() stopVpnAdapter() return super.onUnbind(intent) - } + }*/ } From 3b6d3c1f8e22e6bc88acaff8737b73c3e08b6e5a Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 11 Jul 2024 19:57:25 +0530 Subject: [PATCH 067/188] migration: log database with more index on connIds --- .../java/com/celzero/bravedns/database/LogDatabase.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/celzero/bravedns/database/LogDatabase.kt b/app/src/main/java/com/celzero/bravedns/database/LogDatabase.kt index 7cb88a840..2427781f2 100644 --- a/app/src/main/java/com/celzero/bravedns/database/LogDatabase.kt +++ b/app/src/main/java/com/celzero/bravedns/database/LogDatabase.kt @@ -27,11 +27,12 @@ import androidx.room.TypeConverters import androidx.room.migration.Migration import androidx.sqlite.db.SimpleSQLiteQuery import androidx.sqlite.db.SupportSQLiteDatabase +import androidx.work.impl.Migration_1_2 import com.celzero.bravedns.util.Utilities @Database( entities = [ConnectionTracker::class, DnsLog::class, RethinkLog::class], - version = 8, + version = 9, exportSchema = false ) @TypeConverters(Converters::class) @@ -69,6 +70,7 @@ abstract class LogDatabase : RoomDatabase() { .addMigrations(MIGRATION_5_6) .addMigrations(MIGRATION_6_7) .addMigrations(MIGRATION_7_8) + .addMigrations(Migration_8_9) .fallbackToDestructiveMigration() // recreate the database if no migration is found .build() } @@ -273,6 +275,13 @@ abstract class LogDatabase : RoomDatabase() { ) } } + + private val Migration_8_9: Migration = object : Migration(8, 9) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("CREATE INDEX IF NOT EXISTS index_ConnectionTracker_connId ON ConnectionTracker(connId)") + database.execSQL("CREATE INDEX IF NOT EXISTS index_RethinkLog_connId ON RethinkLog(connId)") + } + } } fun checkPoint() { From 071604bae852047f3c0b97afda4dded337d2fb35 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 11 Jul 2024 19:59:20 +0530 Subject: [PATCH 068/188] tunnel: changes in firestack to use different primitive types --- .../main/java/com/celzero/bravedns/data/AppConfig.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/celzero/bravedns/data/AppConfig.kt b/app/src/main/java/com/celzero/bravedns/data/AppConfig.kt index 0a761d0cd..101e9c32f 100644 --- a/app/src/main/java/com/celzero/bravedns/data/AppConfig.kt +++ b/app/src/main/java/com/celzero/bravedns/data/AppConfig.kt @@ -135,7 +135,7 @@ internal constructor( } } - enum class TunFirewallMode(val mode: Long) { + enum class TunFirewallMode(val mode: Int) { FILTER_ANDROID9_ABOVE(Settings.BlockModeFilter), SINK(Settings.BlockModeSink), FILTER_ANDROID8_BELOW(Settings.BlockModeFilterProc), @@ -195,9 +195,9 @@ internal constructor( } enum class TunDnsMode(val mode: Long) { - NONE(Settings.DNSModeNone), - DNS_IP(Settings.DNSModeIP), - DNS_PORT(Settings.DNSModePort) + NONE(Settings.DNSModeNone.toLong()), + DNS_IP(Settings.DNSModeIP.toLong()), + DNS_PORT(Settings.DNSModePort.toLong()) } // TODO: untangle the mess of proxy modes and providers @@ -335,10 +335,10 @@ internal constructor( } } - enum class ProtoTranslationMode(val id: Long) { + enum class ProtoTranslationMode(val id: Int) { PTMODEAUTO(Settings.PtModeAuto), PTMODEFORCE64(Settings.PtModeForce64), - PTMODEMAYBE46(Settings.PtModeNo46) + PTMODENO46(Settings.PtModeNo46) } fun getInternetProtocol(): InternetProtocol { From f6a19314eef3d9bb786d1e429b053f562d2623ce Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 11 Jul 2024 19:59:33 +0530 Subject: [PATCH 069/188] ui: rmv unused methods --- .../com/celzero/bravedns/ui/activity/AppWiseIpLogsActivity.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseIpLogsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseIpLogsActivity.kt index 92155672f..e631ba92f 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseIpLogsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseIpLogsActivity.kt @@ -77,12 +77,10 @@ class AppWiseIpLogsActivity : isRethink = true init() setRethinkAdapter() - observeRethinkNetworkLogSize() b.toggleGroup.addOnButtonCheckedListener(listViewToggleListener) } else { init() setAdapter() - observeNetworkLogSize() setClickListener() } } From bad8520c8e86103bb15b38ca0cf79e0d970fef86 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 11 Jul 2024 20:03:32 +0530 Subject: [PATCH 070/188] ui: display netstat data in MB instead of MB/s --- .../com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt index 8be701021..53abbfdb9 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt @@ -952,7 +952,7 @@ class HomeScreenFragment : Fragment(R.layout.fragment_home_screen) { val stat = VpnController.getNetStat() val nic = stat?.nic() - // show the stats in MB/s + // show the stats in MB val txBytes = String.format("%.2f", (nic?.txBytes ?: 0) / 1000000.0) val rxBytes = String.format("%.2f", (nic?.rxBytes ?: 0) / 1000000.0) @@ -972,7 +972,7 @@ class HomeScreenFragment : Fragment(R.layout.fragment_home_screen) { getString(R.string.symbol_black_down) ) ) - b.fhsInternetSpeedUnit.text = getString(R.string.symbol_mbs) + b.fhsInternetSpeedUnit.text = getString(R.string.symbol_mb) } private fun stopTrafficStats() { From ca451f12f24ab9e970cd4c87440b731c6373f3a8 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sun, 14 Jul 2024 19:19:01 +0530 Subject: [PATCH 071/188] rmv crashlytics and related dependencies from code --- app/build.gradle | 21 +- app/google-services.json | 29 --- .../java/com/celzero/bravedns/util/Logger.kt | 175 ---------------- .../ui/activity/MiscSettingsActivity.kt | 25 --- .../res/layout/activity_misc_settings.xml | 59 ------ .../bravedns/service/PersistentState.kt | 2 - .../java/com/celzero/bravedns/util/Logger.kt | 22 +- app/src/main/res/drawable/ic_firebase.xml | 9 - .../java/com/celzero/bravedns/util/Logger.kt | 193 ------------------ build.gradle | 4 - 10 files changed, 18 insertions(+), 521 deletions(-) delete mode 100644 app/google-services.json delete mode 100644 app/src/fdroid/java/com/celzero/bravedns/util/Logger.kt rename app/src/{website => main}/java/com/celzero/bravedns/util/Logger.kt (90%) delete mode 100644 app/src/main/res/drawable/ic_firebase.xml delete mode 100644 app/src/play/java/com/celzero/bravedns/util/Logger.kt diff --git a/app/build.gradle b/app/build.gradle index c77ba4a8e..51d203d59 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,8 +4,6 @@ plugins { // to download blocklists for the headless variant id "de.undercouch.download" version "5.3.0" id 'kotlin-android' - id 'com.google.gms.google-services' - id 'com.google.firebase.crashlytics' } def keystorePropertiesFile = rootProject.file("keystore.properties") @@ -173,7 +171,6 @@ configurations { } dependencies { - androidTestImplementation 'androidx.test:rules:1.5.0' def room_version = "2.6.1" def paging_version = "3.3.0" @@ -249,11 +246,11 @@ dependencies { fullImplementation 'com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.9' // from: https://jitpack.io/#celzero/firestack - download 'com.github.celzero:firestack:364ac37a66@aar' - websiteImplementation 'com.github.celzero:firestack:364ac37a66@aar' - fdroidImplementation 'com.github.celzero:firestack:364ac37a66@aar' + download 'com.github.celzero:firestack:1044953166@aar' + websiteImplementation 'com.github.celzero:firestack:1044953166@aar' + fdroidImplementation 'com.github.celzero:firestack:1044953166@aar' // debug symbols for crashlytics - playImplementation 'com.github.celzero:firestack:364ac37a66:debug@aar' + playImplementation 'com.github.celzero:firestack:1044953166:debug@aar' // Work manager implementation('androidx.work:work-runtime-ktx:2.9.0') { @@ -270,8 +267,9 @@ dependencies { implementation 'com.github.seancfoley:ipaddress:5.4.0' testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + androidTestImplementation 'androidx.test.ext:junit:1.2.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1' + androidTestImplementation 'androidx.test:rules:1.6.1' leakCanaryImplementation 'com.squareup.leakcanary:leakcanary-android:2.14' @@ -290,11 +288,6 @@ dependencies { // barcode scanner for wireguard fullImplementation 'com.journeyapps:zxing-android-embedded:4.3.0' - - // only using firebase crashlytics experimentally for stability tracking, only in play variant - // not in fdroid or website - playImplementation 'com.google.firebase:firebase-crashlytics:19.0.1' - playImplementation 'com.google.firebase:firebase-crashlytics-ndk:19.0.1' } // github.com/michel-kraemer/gradle-download-task/issues/131#issuecomment-464476903 diff --git a/app/google-services.json b/app/google-services.json deleted file mode 100644 index 8fe85e0a0..000000000 --- a/app/google-services.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "project_info": { - "project_number": "974915159594", - "project_id": "rethink-dns-firewall", - "storage_bucket": "rethink-dns-firewall.appspot.com" - }, - "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:974915159594:android:ed4f2e6c806fa816bda553", - "android_client_info": { - "package_name": "com.celzero.bravedns" - } - }, - "oauth_client": [], - "api_key": [ - { - "current_key": "AIzaSyBsj6i5hZNgsYopDLZlqV7jFAAp1F0y6JQ" - } - ], - "services": { - "appinvite_service": { - "other_platform_oauth_client": [] - } - } - } - ], - "configuration_version": "1" -} \ No newline at end of file diff --git a/app/src/fdroid/java/com/celzero/bravedns/util/Logger.kt b/app/src/fdroid/java/com/celzero/bravedns/util/Logger.kt deleted file mode 100644 index 30ddf8f19..000000000 --- a/app/src/fdroid/java/com/celzero/bravedns/util/Logger.kt +++ /dev/null @@ -1,175 +0,0 @@ -/* - * Copyright 2021 RethinkDNS and its authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import android.util.Log -import com.celzero.bravedns.service.PersistentState -import com.celzero.bravedns.service.VpnController -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -object Logger : KoinComponent { - private val persistentState by inject() - private val inMemDb by inject() - private var logLevel = persistentState.goLoggerLevel - - const val LOG_TAG_APP_UPDATE = "NonStoreAppUpdater" - const val LOG_TAG_VPN = "VpnLifecycle" - const val LOG_TAG_CONNECTION = "ConnectivityEvents" - const val LOG_TAG_DNS = "DnsManager" - const val LOG_TAG_FIREWALL = "FirewallManager" - const val LOG_BATCH_LOGGER = "BatchLogger" - const val LOG_TAG_APP_DB = "AppDatabase" - const val LOG_TAG_DOWNLOAD = "DownloadManager" - const val LOG_TAG_UI = "ActivityManager" - const val LOG_TAG_SCHEDULER = "JobScheduler" - const val LOG_TAG_BUG_REPORT = "BugReport" - const val LOG_TAG_BACKUP_RESTORE = "BackupRestore" - const val LOG_PROVIDER = "BlocklistProvider" - const val LOG_TAG_PROXY = "ProxyLogs" - const val LOG_QR_CODE = "QrCodeFromFileScanner" - const val LOG_GO_LOGGER = "LibLogger" - - // github.com/celzero/firestack/blob/bce8de917fec5e48a41ed1e96c9d942ee0f7996b/intra/log/logger.go#L76 - enum class LoggerType(val id: Long) { - VERY_VERBOSE(0), - VERBOSE(1), - DEBUG(2), - INFO(3), - WARN(4), - ERROR(5), - STACKTRACE(6), - USR(7), - NONE(8); - - companion object { - fun fromId(id: Int): LoggerType { - return when (id) { - 0 -> VERY_VERBOSE - 1 -> VERBOSE - 2 -> DEBUG - 3 -> INFO - 4 -> WARN - 5 -> ERROR - 6 -> STACKTRACE - 7 -> USR - 8 -> NONE - else -> NONE - } - } - } - - fun stacktrace(): Boolean { - return this == STACKTRACE - } - - fun isLessThan(level: LoggerType): Boolean { - return this.id < level.id - } - - fun user(): Boolean { - return this == USR - } - } - - fun vv(tag: String, message: String) { - log(tag, message, LoggerType.VERY_VERBOSE) - } - - fun v(tag: String, message: String) { - log(tag, message, LoggerType.VERBOSE) - } - - fun d(tag: String, message: String) { - log(tag, message, LoggerType.DEBUG) - } - - fun i(tag: String, message: String) { - log(tag, message, LoggerType.INFO) - } - - fun w(tag: String, message: String, e: Exception? = null) { - log(tag, message, LoggerType.WARN, e) - } - - fun e(tag: String, message: String, e: Exception? = null) { - log(tag, message, LoggerType.ERROR, e) - } - - fun crash(tag: String, message: String, e: Exception?= null) { - log(tag, message, LoggerType.ERROR, e) - } - - fun enableCrashlytics() { - // no-op, crashlytics is not used for F-Droid variant - } - - fun disableCrashlytics() { - // no-op, crashlytics is not used for F-Droid variant - } - - fun updateConfigLevel(level: Long) { - logLevel = level - } - - fun throwableToException(throwable: Throwable): Exception { - return if (throwable is Exception) { - throwable - } else { - Exception(throwable) - } - } - - fun goLog(message: String, type: LoggerType) { - // no need to log the go logs, add it to the database - dbWrite("", message, type) - } - - fun log(tag: String, msg: String, type: LoggerType, e: Exception? = null) { - when (type) { - LoggerType.VERY_VERBOSE -> if (logLevel <= LoggerType.VERY_VERBOSE.id) Log.v(tag, msg) - LoggerType.VERBOSE -> if (logLevel <= LoggerType.VERBOSE.id) Log.v(tag, msg) - LoggerType.DEBUG -> if (logLevel <= LoggerType.DEBUG.id) Log.d(tag, msg) - LoggerType.INFO -> if (logLevel <= LoggerType.INFO.id) Log.i(tag, msg) - LoggerType.WARN -> if (logLevel <= LoggerType.WARN.id) Log.w(tag, msg, e) - LoggerType.ERROR -> if (logLevel <= LoggerType.ERROR.id) Log.e(tag, msg, e) - LoggerType.STACKTRACE -> if (logLevel <= LoggerType.ERROR.id) Log.e(tag, msg, e) - LoggerType.USR -> {} // Do nothing - LoggerType.NONE -> {} // Do nothing - } - dbWrite(tag, msg, type, e) - } - - private fun dbWrite(tag: String, msg: String, type: LoggerType, e: Exception? = null) { - if (type.id >= logLevel) { - // get the first letter of the level and append it to the tag - val l = when (type) { - LoggerType.VERBOSE -> "V" - LoggerType.DEBUG -> "D" - LoggerType.INFO -> "I" - LoggerType.WARN -> "W" - LoggerType.ERROR -> "E" - LoggerType.STACKTRACE -> "E" - else -> "V" - } - val log = if (tag.isEmpty()) { - ConsoleLog(0, msg, System.currentTimeMillis()) - } else { - ConsoleLog(0, "$l $tag: $msg", System.currentTimeMillis()) - } - // insert the log to the database via channel - inMemDb.logChannel.trySend(log) - } - } -} diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/MiscSettingsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/MiscSettingsActivity.kt index ecc221d5f..6386f5610 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/MiscSettingsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/MiscSettingsActivity.kt @@ -110,13 +110,6 @@ class MiscSettingsActivity : AppCompatActivity(R.layout.activity_misc_settings) b.settingsActivityAutoStartSwitch.isChecked = persistentState.prefAutoStartBootUp // check for app updates b.settingsActivityCheckUpdateSwitch.isChecked = persistentState.checkForAppUpdate - // crashlytics - if (isPlayStoreFlavour()) { - b.settingsCrashlyticsRl.visibility = View.VISIBLE - b.settingsActivityCrashlyticsSwitch.isChecked = persistentState.crashlyticsEnabled - } else { - b.settingsCrashlyticsRl.visibility = View.GONE - } // for app locale (default system/user selected locale) if (isAtleastT()) { @@ -316,24 +309,6 @@ class MiscSettingsActivity : AppCompatActivity(R.layout.activity_misc_settings) persistentState.biometricAuthTime = Constants.INIT_TIME_MS } - b.settingsCrashlyticsRl.setOnClickListener { - b.settingsActivityCrashlyticsSwitch.isChecked = - !b.settingsActivityCrashlyticsSwitch.isChecked - } - - b.settingsActivityCrashlyticsSwitch.setOnCheckedChangeListener { _: CompoundButton, b: Boolean - -> - // only for play store version - persistentState.crashlyticsEnabled = b - if (b) { - // enable crashlytics - Logger.enableCrashlytics() - } else { - // disable crashlytics - Logger.disableCrashlytics() - } - } - b.settingsConsoleLogRl.setOnClickListener { openConsoleLogActivity() } diff --git a/app/src/full/res/layout/activity_misc_settings.xml b/app/src/full/res/layout/activity_misc_settings.xml index 93a86ea63..bde3caed1 100644 --- a/app/src/full/res/layout/activity_misc_settings.xml +++ b/app/src/full/res/layout/activity_misc_settings.xml @@ -750,65 +750,6 @@ - - - - - - - - - - - - - - - - - (true) - var crashlyticsEnabled by booleanPref("crashlytics_enabled").withDefault(Utilities.isPlayStoreFlavour()) - var pingv4Ips by stringPref("ping_ipv4_ips").withDefault(Constants.ip4probes.joinToString(",")) var pingv6Ips by stringPref("ping_ipv6_ips").withDefault(Constants.ip6probes.joinToString(",")) diff --git a/app/src/website/java/com/celzero/bravedns/util/Logger.kt b/app/src/main/java/com/celzero/bravedns/util/Logger.kt similarity index 90% rename from app/src/website/java/com/celzero/bravedns/util/Logger.kt rename to app/src/main/java/com/celzero/bravedns/util/Logger.kt index cf7ed2814..f3e3ec628 100644 --- a/app/src/website/java/com/celzero/bravedns/util/Logger.kt +++ b/app/src/main/java/com/celzero/bravedns/util/Logger.kt @@ -17,6 +17,7 @@ import android.util.Log import com.celzero.bravedns.database.ConsoleLog import com.celzero.bravedns.database.ConsoleLogRepository import com.celzero.bravedns.service.PersistentState +import com.celzero.bravedns.service.VpnController import org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -42,7 +43,7 @@ object Logger : KoinComponent { const val LOG_QR_CODE = "QrCodeFromFileScanner" const val LOG_GO_LOGGER = "LibLogger" - // github.com/celzero/firestack/blob/bce8de917fec5e48a41ed1e96c9d942ee0f7996b/intra/log/logger.go#L76 + // github.com/celzero/firestack/blob/bce8de917f/intra/log/logger.go#L76 enum class LoggerType(val id: Long) { VERY_VERBOSE(0), VERBOSE(1), @@ -112,14 +113,6 @@ object Logger : KoinComponent { log(tag, message, LoggerType.ERROR, e) } - fun enableCrashlytics() { - // no-op, Crashlytics is not used for website variant - } - - fun disableCrashlytics() { - // no-op, Crashlytics is not used for website variant - } - fun updateConfigLevel(level: Long) { logLevel = level } @@ -153,10 +146,16 @@ object Logger : KoinComponent { } private fun dbWrite(tag: String, msg: String, type: LoggerType, e: Exception? = null) { + // write to the database only if console log is set to true + if (!persistentState.consoleLogEnabled) return + try { + // cannot check for log levels when tag is empty; tag is empty for logs coming from go if (tag.isEmpty()) { val log = ConsoleLog(0, msg, System.currentTimeMillis()) - inMemDb.logChannel.trySend(log) + VpnController.writeConsoleLog(log) + // TODO: use send instead of trySend + //inMemDb.logChannel.trySend(log) } else if (type.id >= logLevel) { val l = when (type) { LoggerType.VERBOSE -> "V" @@ -176,7 +175,8 @@ object Logger : KoinComponent { } else { ConsoleLog(0, "$l $tag: $msg", System.currentTimeMillis()) } - inMemDb.logChannel.trySend(log) + //inMemDb.logChannel.trySend(log) + VpnController.writeConsoleLog(log) } else { // Do nothing } diff --git a/app/src/main/res/drawable/ic_firebase.xml b/app/src/main/res/drawable/ic_firebase.xml deleted file mode 100644 index eb80dcbd4..000000000 --- a/app/src/main/res/drawable/ic_firebase.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/play/java/com/celzero/bravedns/util/Logger.kt b/app/src/play/java/com/celzero/bravedns/util/Logger.kt deleted file mode 100644 index 0f26a6b43..000000000 --- a/app/src/play/java/com/celzero/bravedns/util/Logger.kt +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright 2021 RethinkDNS and its authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import android.util.Log -import com.celzero.bravedns.service.PersistentState -import com.celzero.bravedns.util.Utilities -import com.celzero.bravedns.service.VpnController -import com.google.firebase.crashlytics.FirebaseCrashlytics -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -object Logger : KoinComponent { - private val persistentState by inject() - private val inMemDb by inject() - private var logLevel = persistentState.goLoggerLevel - - const val LOG_TAG_APP_UPDATE = "NonStoreAppUpdater" - const val LOG_TAG_VPN = "VpnLifecycle" - const val LOG_TAG_CONNECTION = "ConnectivityEvents" - const val LOG_TAG_DNS = "DnsManager" - const val LOG_TAG_FIREWALL = "FirewallManager" - const val LOG_BATCH_LOGGER = "BatchLogger" - const val LOG_TAG_APP_DB = "AppDatabase" - const val LOG_TAG_DOWNLOAD = "DownloadManager" - const val LOG_TAG_UI = "ActivityManager" - const val LOG_TAG_SCHEDULER = "JobScheduler" - const val LOG_TAG_BUG_REPORT = "BugReport" - const val LOG_TAG_BACKUP_RESTORE = "BackupRestore" - const val LOG_PROVIDER = "BlocklistProvider" - const val LOG_TAG_PROXY = "ProxyLogs" - const val LOG_QR_CODE = "QrCodeFromFileScanner" - const val LOG_GO_LOGGER = "LibLogger" - - // github.com/celzero/firestack/blob/bce8de917fec5e48a41ed1e96c9d942ee0f7996b/intra/log/logger.go#L76 - enum class LoggerType(val id: Long) { - VERY_VERBOSE(0), - VERBOSE(1), - DEBUG(2), - INFO(3), - WARN(4), - ERROR(5), - STACKTRACE(6), - USR(7), - NONE(8); - - companion object { - fun fromId(id: Int): LoggerType { - return when (id) { - 0 -> VERY_VERBOSE - 1 -> VERBOSE - 2 -> DEBUG - 3 -> INFO - 4 -> WARN - 5 -> ERROR - 6 -> STACKTRACE - 7 -> USR - 8 -> NONE - else -> NONE - } - } - } - - fun stacktrace(): Boolean { - return this == STACKTRACE - } - - fun isLessThan(level: LoggerType): Boolean { - return this.id < level.id - } - - fun user(): Boolean { - return this == USR - } - } - - fun vv(tag: String, message: String) { - log(tag, message, LoggerType.VERY_VERBOSE) - } - - fun v(tag: String, message: String) { - log(tag, message, LoggerType.VERBOSE) - } - - fun d(tag: String, message: String) { - log(tag, message, LoggerType.DEBUG) - } - - fun i(tag: String, message: String) { - log(tag, message, LoggerType.INFO) - } - - fun w(tag: String, message: String, e: Exception? = null) { - log(tag, message, LoggerType.WARN, e) - } - - fun e(tag: String, message: String, e: Exception? = null) { - log(tag, message, LoggerType.ERROR, e) - } - - fun crash(tag: String, message: String, e: Exception? = null) { - log(tag, message, LoggerType.ERROR, e) - if (Utilities.isPlayStoreFlavour()) { - try { - val crashlytics = FirebaseCrashlytics.getInstance() - crashlytics.log("$tag: $message") - if (e != null) crashlytics.recordException(e) - else crashlytics.recordException(Exception(message)) - // send the unsent reports, if any as the crash is important to be reported. - crashlytics.sendUnsentReports() - } catch (ex: Exception) { - Log.e(LOG_TAG_APP_UPDATE, "Error in logging to crashlytics: ${ex.message}") - } - } - } - - fun enableCrashlytics() { - if (Utilities.isPlayStoreFlavour()) { - FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(true) - } - } - - fun disableCrashlytics() { - if (Utilities.isPlayStoreFlavour()) { - FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(false) - } - } - - fun updateConfigLevel(level: Long) { - logLevel = level - } - - fun throwableToException(throwable: Throwable): Exception { - return if (throwable is Exception) { - throwable - } else { - Exception(throwable) - } - } - - fun goLog(message: String, type: LoggerType) { - // no need to log the go logs, add it to the database - dbWrite("", message, type) - } - - fun log(tag: String, msg: String, type: LoggerType, e: Exception? = null) { - when (type) { - LoggerType.VERY_VERBOSE -> if (logLevel <= LoggerType.VERY_VERBOSE.id) Log.v(tag, msg) - LoggerType.VERBOSE -> if (logLevel <= LoggerType.VERBOSE.id) Log.v(tag, msg) - LoggerType.DEBUG -> if (logLevel <= LoggerType.DEBUG.id) Log.d(tag, msg) - LoggerType.INFO -> if (logLevel <= LoggerType.INFO.id) Log.i(tag, msg) - LoggerType.WARN -> if (logLevel <= LoggerType.WARN.id) Log.w(tag, msg, e) - LoggerType.ERROR -> if (logLevel <= LoggerType.ERROR.id) Log.e(tag, msg, e) - LoggerType.STACKTRACE -> if (logLevel <= LoggerType.ERROR.id) Log.e(tag, msg, e) - LoggerType.USR -> {} // Do nothing - LoggerType.NONE -> {} // Do nothing - } - dbWrite(tag, msg, type, e) - } - - private fun dbWrite(tag: String, msg: String, type: LoggerType, e: Exception? = null) { - if (type.id >= logLevel) { - // get the first letter of the level and append it to the tag - val l = when (type) { - LoggerType.VERBOSE -> "V" - LoggerType.DEBUG -> "D" - LoggerType.INFO -> "I" - LoggerType.WARN -> "W" - LoggerType.ERROR -> "E" - LoggerType.STACKTRACE -> "E" - else -> "V" - } - val log = if (tag.isEmpty()) { - ConsoleLog(0, msg, System.currentTimeMillis()) - } else { - ConsoleLog(0, "$l $tag: $msg", System.currentTimeMillis()) - } - // insert the log to the database via channel - inMemDb.logChannel.trySend(log) - } - } -} diff --git a/build.gradle b/build.gradle index 5f9e8e2ef..04f65102c 100644 --- a/build.gradle +++ b/build.gradle @@ -11,10 +11,6 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:8.3.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - classpath 'com.google.gms:google-services:4.4.1' - classpath 'com.google.firebase:firebase-crashlytics-gradle:3.0.1' } } From fd66e9c0a80050b6c11e75cc06a65f877566e3a2 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sun, 14 Jul 2024 19:19:53 +0530 Subject: [PATCH 072/188] ui: improvements in recycler view's DIFF_CALLBACK --- .../bravedns/adapter/CustomDomainAdapter.kt | 3 +- .../bravedns/adapter/CustomIpAdapter.kt | 3 +- .../adapter/FirewallAppListAdapter.kt | 82 ++++++----------- .../bravedns/adapter/WgIncludeAppsAdapter.kt | 3 +- .../bravedns/ui/activity/AppListActivity.kt | 91 ++++++++----------- .../FirewallAppFilterBottomSheet.kt | 30 +++--- .../com/celzero/bravedns/database/AppInfo.kt | 7 +- .../celzero/bravedns/database/AppInfoDAO.kt | 2 +- .../celzero/bravedns/database/CustomDomain.kt | 5 +- .../com/celzero/bravedns/database/CustomIp.kt | 6 +- .../database/ProxyApplicationMapping.kt | 8 +- 11 files changed, 104 insertions(+), 136 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/CustomDomainAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/CustomDomainAdapter.kt index 3abd6f0bb..b3f72cbff 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/CustomDomainAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/CustomDomainAdapter.kt @@ -77,8 +77,7 @@ class CustomDomainAdapter(val context: Context, val rule: CustomRulesActivity.RU oldConnection: CustomDomain, newConnection: CustomDomain ): Boolean { - return (oldConnection.domain == newConnection.domain && - oldConnection.status != newConnection.status) + return oldConnection == newConnection } } } diff --git a/app/src/full/java/com/celzero/bravedns/adapter/CustomIpAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/CustomIpAdapter.kt index a18e69b6b..de8301a03 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/CustomIpAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/CustomIpAdapter.kt @@ -72,8 +72,7 @@ class CustomIpAdapter(private val context: Context, private val type: CustomRule oldConnection.status == newConnection.status override fun areContentsTheSame(oldConnection: CustomIp, newConnection: CustomIp) = - oldConnection.ipAddress == newConnection.ipAddress && - oldConnection.status != newConnection.status + oldConnection == newConnection } } diff --git a/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt index 4133bcdbd..e38349810 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt @@ -43,10 +43,10 @@ import com.celzero.bravedns.ui.activity.AppInfoActivity.Companion.UID_INTENT_NAM import com.celzero.bravedns.util.Utilities import com.celzero.bravedns.util.Utilities.getIcon import com.google.android.material.dialog.MaterialAlertDialogBuilder +import java.util.concurrent.TimeUnit import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import java.util.concurrent.TimeUnit class FirewallAppListAdapter( private val context: Context, @@ -60,16 +60,14 @@ class FirewallAppListAdapter( oldConnection: AppInfo, newConnection: AppInfo ): Boolean { - return oldConnection == newConnection + return oldConnection.packageName == newConnection.packageName } override fun areContentsTheSame( oldConnection: AppInfo, newConnection: AppInfo ): Boolean { - return (oldConnection.packageName == newConnection.packageName && - oldConnection.firewallStatus == newConnection.firewallStatus && - oldConnection.connectionStatus == newConnection.connectionStatus) + return oldConnection == newConnection } } } @@ -101,9 +99,7 @@ class FirewallAppListAdapter( b.firewallAppLabelTv.text = appInfo.appName b.firewallAppToggleOther.text = getFirewallText(appStatus, connStatus) displayIcon( - getIcon(context, appInfo.packageName, appInfo.appName), - b.firewallAppIconIv - ) + getIcon(context, appInfo.packageName, appInfo.appName), b.firewallAppIconIv) if (appInfo.packageName == context.packageName) { b.firewallAppToggleWifi.visibility = View.GONE b.firewallAppToggleMobileData.visibility = View.GONE @@ -199,38 +195,32 @@ class FirewallAppListAdapter( private fun showMobileDataDisabled() { b.firewallAppToggleMobileData.setImageDrawable( - ContextCompat.getDrawable(context, R.drawable.ic_firewall_data_off) - ) + ContextCompat.getDrawable(context, R.drawable.ic_firewall_data_off)) } private fun showMobileDataEnabled() { b.firewallAppToggleMobileData.setImageDrawable( - ContextCompat.getDrawable(context, R.drawable.ic_firewall_data_on) - ) + ContextCompat.getDrawable(context, R.drawable.ic_firewall_data_on)) } private fun showWifiDisabled() { b.firewallAppToggleWifi.setImageDrawable( - ContextCompat.getDrawable(context, R.drawable.ic_firewall_wifi_off) - ) + ContextCompat.getDrawable(context, R.drawable.ic_firewall_wifi_off)) } private fun showWifiEnabled() { b.firewallAppToggleWifi.setImageDrawable( - ContextCompat.getDrawable(context, R.drawable.ic_firewall_wifi_on) - ) + ContextCompat.getDrawable(context, R.drawable.ic_firewall_wifi_on)) } private fun showMobileDataUnused() { b.firewallAppToggleMobileData.setImageDrawable( - ContextCompat.getDrawable(context, R.drawable.ic_firewall_data_on_grey) - ) + ContextCompat.getDrawable(context, R.drawable.ic_firewall_data_on_grey)) } private fun showWifiUnused() { b.firewallAppToggleWifi.setImageDrawable( - ContextCompat.getDrawable(context, R.drawable.ic_firewall_wifi_on_grey) - ) + ContextCompat.getDrawable(context, R.drawable.ic_firewall_wifi_on_grey)) } private fun showAppHint(mIconIndicator: TextView, appInfo: AppInfo) { @@ -243,45 +233,37 @@ class FirewallAppListAdapter( when (connStatus) { FirewallManager.ConnectionStatus.ALLOW -> { mIconIndicator.setBackgroundColor( - context.getColor(R.color.colorGreen_900) - ) + context.getColor(R.color.colorGreen_900)) } FirewallManager.ConnectionStatus.METERED -> { mIconIndicator.setBackgroundColor( - context.getColor(R.color.colorAmber_900) - ) + context.getColor(R.color.colorAmber_900)) } FirewallManager.ConnectionStatus.UNMETERED -> { mIconIndicator.setBackgroundColor( - context.getColor(R.color.colorAmber_900) - ) + context.getColor(R.color.colorAmber_900)) } FirewallManager.ConnectionStatus.BOTH -> { mIconIndicator.setBackgroundColor( - context.getColor(R.color.colorAmber_900) - ) + context.getColor(R.color.colorAmber_900)) } } } FirewallManager.FirewallStatus.EXCLUDE -> { mIconIndicator.setBackgroundColor( - context.getColor(R.color.primaryLightColorText) - ) + context.getColor(R.color.primaryLightColorText)) } FirewallManager.FirewallStatus.BYPASS_UNIVERSAL -> { mIconIndicator.setBackgroundColor( - context.getColor(R.color.primaryLightColorText) - ) + context.getColor(R.color.primaryLightColorText)) } FirewallManager.FirewallStatus.BYPASS_DNS_FIREWALL -> { mIconIndicator.setBackgroundColor( - context.getColor(R.color.primaryLightColorText) - ) + context.getColor(R.color.primaryLightColorText)) } FirewallManager.FirewallStatus.ISOLATE -> { mIconIndicator.setBackgroundColor( - context.getColor(R.color.colorAmber_900) - ) + context.getColor(R.color.colorAmber_900)) } FirewallManager.FirewallStatus.UNTRACKED -> { /* no-op */ @@ -360,29 +342,25 @@ class FirewallAppListAdapter( updateFirewallStatus( appInfo.uid, FirewallManager.FirewallStatus.NONE, - FirewallManager.ConnectionStatus.ALLOW - ) + FirewallManager.ConnectionStatus.ALLOW) } FirewallManager.ConnectionStatus.UNMETERED -> { updateFirewallStatus( appInfo.uid, FirewallManager.FirewallStatus.NONE, - FirewallManager.ConnectionStatus.BOTH - ) + FirewallManager.ConnectionStatus.BOTH) } FirewallManager.ConnectionStatus.BOTH -> { updateFirewallStatus( appInfo.uid, FirewallManager.FirewallStatus.NONE, - FirewallManager.ConnectionStatus.UNMETERED - ) + FirewallManager.ConnectionStatus.UNMETERED) } FirewallManager.ConnectionStatus.ALLOW -> { updateFirewallStatus( appInfo.uid, FirewallManager.FirewallStatus.NONE, - FirewallManager.ConnectionStatus.METERED - ) + FirewallManager.ConnectionStatus.METERED) } } } @@ -399,29 +377,25 @@ class FirewallAppListAdapter( updateFirewallStatus( appInfo.uid, FirewallManager.FirewallStatus.NONE, - FirewallManager.ConnectionStatus.BOTH - ) + FirewallManager.ConnectionStatus.BOTH) } FirewallManager.ConnectionStatus.UNMETERED -> { updateFirewallStatus( appInfo.uid, FirewallManager.FirewallStatus.NONE, - FirewallManager.ConnectionStatus.ALLOW - ) + FirewallManager.ConnectionStatus.ALLOW) } FirewallManager.ConnectionStatus.BOTH -> { updateFirewallStatus( appInfo.uid, FirewallManager.FirewallStatus.NONE, - FirewallManager.ConnectionStatus.METERED - ) + FirewallManager.ConnectionStatus.METERED) } FirewallManager.ConnectionStatus.ALLOW -> { updateFirewallStatus( appInfo.uid, FirewallManager.FirewallStatus.NONE, - FirewallManager.ConnectionStatus.UNMETERED - ) + FirewallManager.ConnectionStatus.UNMETERED) } } } @@ -444,8 +418,8 @@ class FirewallAppListAdapter( builderSingle.setIcon(R.drawable.ic_firewall_block_grey) val count = packageList.count() builderSingle.setTitle( - context.getString(R.string.ctbs_block_other_apps, appInfo.appName, count.toString()) - ) + context.getString( + R.string.ctbs_block_other_apps, appInfo.appName, count.toString())) val arrayAdapter = ArrayAdapter(context, android.R.layout.simple_list_item_activated_1) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/WgIncludeAppsAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/WgIncludeAppsAdapter.kt index ef6e671e4..ee8be984a 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/WgIncludeAppsAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/WgIncludeAppsAdapter.kt @@ -74,8 +74,7 @@ class WgIncludeAppsAdapter( oldConnection: ProxyApplicationMapping, newConnection: ProxyApplicationMapping ): Boolean { - return (oldConnection.proxyId == newConnection.proxyId && - oldConnection.uid == newConnection.uid) + return oldConnection == newConnection } } } diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/AppListActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/AppListActivity.kt index 37414bcdb..c9d30197d 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/AppListActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/AppListActivity.kt @@ -20,6 +20,8 @@ import android.content.res.Configuration import android.graphics.PorterDuff import android.graphics.PorterDuffColorFilter import android.os.Bundle +import android.os.Handler +import android.os.Looper import android.view.LayoutInflater import android.view.View import android.view.animation.Animation @@ -33,6 +35,7 @@ import androidx.appcompat.widget.TooltipCompat import androidx.core.content.ContextCompat import androidx.lifecycle.MutableLiveData import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import by.kirich1409.viewbindingdelegate.viewBinding import com.celzero.bravedns.R @@ -41,7 +44,6 @@ import com.celzero.bravedns.database.RefreshDatabase import com.celzero.bravedns.databinding.ActivityAppListBinding import com.celzero.bravedns.service.PersistentState import com.celzero.bravedns.ui.bottomsheet.FirewallAppFilterBottomSheet -import com.celzero.bravedns.util.CustomLinearLayoutManager import com.celzero.bravedns.util.Themes import com.celzero.bravedns.util.UIUtils import com.celzero.bravedns.util.Utilities @@ -77,7 +79,7 @@ class AppListActivity : private const val ANIMATION_END_DEGREE = 360.0f private const val REFRESH_TIMEOUT: Long = 4000 - private const val QUERY_TEXT_TIMEOUT: Long = 600 + private const val QUERY_TEXT_TIMEOUT: Long = 1000 } // enum class for bulk ui update @@ -200,11 +202,8 @@ class AppListActivity : if (it == null) return@observe - ui { - appInfoViewModel.setFilter(it) - b.ffaAppList.smoothScrollToPosition(0) - updateFilterText(it) - } + appInfoViewModel.setFilter(it) + updateFilterText(it) } } @@ -217,9 +216,7 @@ class AppListActivity : getString( R.string.fapps_firewall_filter_desc, firewallLabel.lowercase(), - filterLabel - ) - ) + filterLabel)) } else { b.firewallAppLabelTv.text = UIUtils.updateHtmlEncodedText( @@ -227,9 +224,7 @@ class AppListActivity : R.string.fapps_firewall_filter_desc_category, firewallLabel.lowercase(), filterLabel, - filter.categoryFilters - ) - ) + filter.categoryFilters)) } b.firewallAppLabelTv.isSelected = true } @@ -278,10 +273,7 @@ class AppListActivity : b.ffaRefreshList.isEnabled = true b.ffaRefreshList.clearAnimation() Utilities.showToastUiCentered( - this, - getString(R.string.refresh_complete), - Toast.LENGTH_SHORT - ) + this, getString(R.string.refresh_complete), Toast.LENGTH_SHORT) } } } @@ -290,30 +282,27 @@ class AppListActivity : showBulkRulesUpdateDialog( getBulkActionDialogTitle(BlockType.UNMETER), getBulkActionDialogMessage(BlockType.UNMETER), - BlockType.UNMETER - ) + BlockType.UNMETER) } b.ffaToggleAllMobileData.setOnClickListener { showBulkRulesUpdateDialog( getBulkActionDialogTitle(BlockType.METER), getBulkActionDialogMessage(BlockType.METER), - BlockType.METER - ) + BlockType.METER) } b.ffaToggleAllLockdown.setOnClickListener { showBulkRulesUpdateDialog( getBulkActionDialogTitle(BlockType.LOCKDOWN), getBulkActionDialogMessage(BlockType.LOCKDOWN), - BlockType.LOCKDOWN - ) + BlockType.LOCKDOWN) } TooltipCompat.setTooltipText( b.ffaToggleAllBypassDnsFirewall, - getString(R.string.bypass_dns_firewall_tooltip, getString(R.string.bypass_dns_firewall)) - ) + getString( + R.string.bypass_dns_firewall_tooltip, getString(R.string.bypass_dns_firewall))) b.ffaToggleAllBypassDnsFirewall.setOnClickListener { // show tooltip once the user clicks on the button @@ -326,24 +315,21 @@ class AppListActivity : showBulkRulesUpdateDialog( getBulkActionDialogTitle(BlockType.BYPASS_DNS_FIREWALL), getBulkActionDialogMessage(BlockType.BYPASS_DNS_FIREWALL), - BlockType.BYPASS_DNS_FIREWALL - ) + BlockType.BYPASS_DNS_FIREWALL) } b.ffaToggleAllBypass.setOnClickListener { showBulkRulesUpdateDialog( getBulkActionDialogTitle(BlockType.BYPASS), getBulkActionDialogMessage(BlockType.BYPASS), - BlockType.BYPASS - ) + BlockType.BYPASS) } b.ffaToggleAllExclude.setOnClickListener { showBulkRulesUpdateDialog( getBulkActionDialogTitle(BlockType.EXCLUDE), getBulkActionDialogMessage(BlockType.EXCLUDE), - BlockType.EXCLUDE - ) + BlockType.EXCLUDE) } b.ffaAppInfoIcon.setOnClickListener { showInfoDialog() } @@ -509,20 +495,17 @@ class AppListActivity : makeFirewallChip( FirewallFilter.BYPASS.id, getString(R.string.fapps_firewall_filter_bypass_universal), - false - ) + false) val excluded = makeFirewallChip( FirewallFilter.EXCLUDED.id, getString(R.string.fapps_firewall_filter_excluded), - false - ) + false) val lockdown = makeFirewallChip( FirewallFilter.LOCKDOWN.id, getString(R.string.fapps_firewall_filter_isolate), - false - ) + false) b.ffaFirewallChipGroup.addView(none) b.ffaFirewallChipGroup.addView(allowed) @@ -567,9 +550,7 @@ class AppListActivity : private fun colorUpChipIcon(chip: Chip) { val colorFilter = PorterDuffColorFilter( - ContextCompat.getColor(this, R.color.primaryText), - PorterDuff.Mode.SRC_IN - ) + ContextCompat.getColor(this, R.color.primaryText), PorterDuff.Mode.SRC_IN) chip.checkedIcon?.colorFilter = colorFilter chip.chipIcon?.colorFilter = colorFilter } @@ -583,8 +564,7 @@ class AppListActivity : b.ffaToggleAllBypass.setImageResource(R.drawable.ic_firewall_bypass_off) b.ffaToggleAllLockdown.setImageResource(R.drawable.ic_firewall_lockdown_off) b.ffaToggleAllBypassDnsFirewall.setImageResource( - R.drawable.ic_bypass_dns_firewall_off - ) + R.drawable.ic_bypass_dns_firewall_off) } BlockType.METER -> { b.ffaToggleAllWifi.setImageResource(R.drawable.ic_firewall_wifi_on_grey) @@ -592,8 +572,7 @@ class AppListActivity : b.ffaToggleAllBypass.setImageResource(R.drawable.ic_firewall_bypass_off) b.ffaToggleAllLockdown.setImageResource(R.drawable.ic_firewall_lockdown_off) b.ffaToggleAllBypassDnsFirewall.setImageResource( - R.drawable.ic_bypass_dns_firewall_off - ) + R.drawable.ic_bypass_dns_firewall_off) } BlockType.LOCKDOWN -> { b.ffaToggleAllMobileData.setImageResource(R.drawable.ic_firewall_data_on_grey) @@ -601,8 +580,7 @@ class AppListActivity : b.ffaToggleAllExclude.setImageResource(R.drawable.ic_firewall_exclude_off) b.ffaToggleAllBypass.setImageResource(R.drawable.ic_firewall_bypass_off) b.ffaToggleAllBypassDnsFirewall.setImageResource( - R.drawable.ic_bypass_dns_firewall_off - ) + R.drawable.ic_bypass_dns_firewall_off) } BlockType.BYPASS -> { b.ffaToggleAllMobileData.setImageResource(R.drawable.ic_firewall_data_on_grey) @@ -610,8 +588,7 @@ class AppListActivity : b.ffaToggleAllExclude.setImageResource(R.drawable.ic_firewall_exclude_off) b.ffaToggleAllLockdown.setImageResource(R.drawable.ic_firewall_lockdown_off) b.ffaToggleAllBypassDnsFirewall.setImageResource( - R.drawable.ic_bypass_dns_firewall_off - ) + R.drawable.ic_bypass_dns_firewall_off) } BlockType.BYPASS_DNS_FIREWALL -> { b.ffaToggleAllMobileData.setImageResource(R.drawable.ic_firewall_data_on_grey) @@ -626,8 +603,7 @@ class AppListActivity : b.ffaToggleAllBypass.setImageResource(R.drawable.ic_firewall_bypass_off) b.ffaToggleAllLockdown.setImageResource(R.drawable.ic_firewall_lockdown_off) b.ffaToggleAllBypassDnsFirewall.setImageResource( - R.drawable.ic_bypass_dns_firewall_off - ) + R.drawable.ic_bypass_dns_firewall_off) } } } @@ -722,11 +698,17 @@ class AppListActivity : } private fun initListAdapter() { + val recyclerAdapter = FirewallAppListAdapter(this, this) b.ffaAppList.setHasFixedSize(true) - layoutManager = CustomLinearLayoutManager(this) + layoutManager = LinearLayoutManager(this) b.ffaAppList.layoutManager = layoutManager - val recyclerAdapter = FirewallAppListAdapter(this, this) - appInfoViewModel.appInfo.observe(this) { recyclerAdapter.submitData(this.lifecycle, it) } + recyclerAdapter.stateRestorationPolicy = + RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY + + appInfoViewModel.appInfo.observe(this) { + b.ffaAppList.post { recyclerAdapter.submitData(lifecycle, it) } + } + b.ffaAppList.adapter = recyclerAdapter } @@ -743,8 +725,7 @@ class AppListActivity : Animation.RELATIVE_TO_SELF, ANIMATION_PIVOT_VALUE, Animation.RELATIVE_TO_SELF, - ANIMATION_PIVOT_VALUE - ) + ANIMATION_PIVOT_VALUE) animation.repeatCount = ANIMATION_REPEAT_COUNT animation.duration = ANIMATION_DURATION } diff --git a/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/FirewallAppFilterBottomSheet.kt b/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/FirewallAppFilterBottomSheet.kt index 65d5352af..2e8d4eb4f 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/FirewallAppFilterBottomSheet.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/FirewallAppFilterBottomSheet.kt @@ -47,7 +47,7 @@ class FirewallAppFilterBottomSheet : BottomSheetDialogFragment() { get() = _binding!! private val persistentState by inject() - private val sortValues = AppListActivity.Filters() + private val filters = AppListActivity.Filters() override fun getTheme(): Int = Themes.getBottomsheetCurrentTheme(isDarkThemeOn(), persistentState.theme) @@ -68,23 +68,23 @@ class FirewallAppFilterBottomSheet : BottomSheetDialogFragment() { } private fun initView() { - val filters = AppListActivity.filters.value + val f = AppListActivity.filters.value remakeParentFilterChipsUi() - if (filters == null) { + if (f == null) { applyParentFilter(AppListActivity.TopLevelFilter.ALL.id) return } else { - sortValues.firewallFilter = filters.firewallFilter + this.filters.firewallFilter = f.firewallFilter } - applyParentFilter(filters.topLevelFilter.id) - setFilter(filters.topLevelFilter, filters.categoryFilters) + applyParentFilter(f.topLevelFilter.id) + setFilter(f.topLevelFilter, f.categoryFilters) } private fun initClickListeners() { b.fsApply.setOnClickListener { - AppListActivity.filters.postValue(sortValues) + AppListActivity.filters.postValue(filters) this.dismiss() } @@ -173,24 +173,24 @@ class FirewallAppFilterBottomSheet : BottomSheetDialogFragment() { private fun applyParentFilter(tag: Any) { when (tag) { AppListActivity.TopLevelFilter.ALL.id -> { - sortValues.topLevelFilter = AppListActivity.TopLevelFilter.ALL - sortValues.categoryFilters.clear() + filters.topLevelFilter = AppListActivity.TopLevelFilter.ALL + filters.categoryFilters.clear() io { val categories = FirewallManager.getAllCategories() uiCtx { remakeChildFilterChipsUi(categories) } } } AppListActivity.TopLevelFilter.INSTALLED.id -> { - sortValues.topLevelFilter = AppListActivity.TopLevelFilter.INSTALLED - sortValues.categoryFilters.clear() + filters.topLevelFilter = AppListActivity.TopLevelFilter.INSTALLED + filters.categoryFilters.clear() io { val categories = FirewallManager.getCategoriesForInstalledApps() uiCtx { remakeChildFilterChipsUi(categories) } } } AppListActivity.TopLevelFilter.SYSTEM.id -> { - sortValues.topLevelFilter = AppListActivity.TopLevelFilter.SYSTEM - sortValues.categoryFilters.clear() + filters.topLevelFilter = AppListActivity.TopLevelFilter.SYSTEM + filters.categoryFilters.clear() io { val categories = FirewallManager.getCategoriesForSystemApps() uiCtx { remakeChildFilterChipsUi(categories) } @@ -220,9 +220,9 @@ class FirewallAppFilterBottomSheet : BottomSheetDialogFragment() { private fun applyChildFilter(tag: Any, show: Boolean) { if (show) { - sortValues.categoryFilters.add(tag.toString()) + filters.categoryFilters.add(tag.toString()) } else { - sortValues.categoryFilters.remove(tag.toString()) + filters.categoryFilters.remove(tag.toString()) } } diff --git a/app/src/main/java/com/celzero/bravedns/database/AppInfo.kt b/app/src/main/java/com/celzero/bravedns/database/AppInfo.kt index aac897e24..4f2d426ad 100644 --- a/app/src/main/java/com/celzero/bravedns/database/AppInfo.kt +++ b/app/src/main/java/com/celzero/bravedns/database/AppInfo.kt @@ -39,11 +39,16 @@ class AppInfo { override fun equals(other: Any?): Boolean { if (other !is AppInfo) return false if (packageName != other.packageName) return false + if (firewallStatus != other.firewallStatus) return false + if (connectionStatus != other.connectionStatus) return false return true } override fun hashCode(): Int { - return this.packageName.hashCode() + var result = this.packageName.hashCode() + result += result * 31 + this.firewallStatus + result += result * 31 + this.connectionStatus + return result } constructor(values: ContentValues?) { diff --git a/app/src/main/java/com/celzero/bravedns/database/AppInfoDAO.kt b/app/src/main/java/com/celzero/bravedns/database/AppInfoDAO.kt index fba6d1f06..781a6a307 100644 --- a/app/src/main/java/com/celzero/bravedns/database/AppInfoDAO.kt +++ b/app/src/main/java/com/celzero/bravedns/database/AppInfoDAO.kt @@ -101,7 +101,7 @@ interface AppInfoDAO { ): PagingSource @Query( - "select * from AppInfo where (appName like :search or uid like :search or packageName like :search) and appCategory in (:filter) and firewallStatus in (:firewall) and connectionStatus in (:connectionStatus) order by lower(appName)" + "select * from AppInfo where (appName like :search or uid like :search or packageName like :search) and appCategory in (:filter) and firewallStatus in (:firewall) and connectionStatus in (:connectionStatus) order by lower(appName)" ) fun getAppInfos( search: String, diff --git a/app/src/main/java/com/celzero/bravedns/database/CustomDomain.kt b/app/src/main/java/com/celzero/bravedns/database/CustomDomain.kt index af1bcfe4b..651255003 100644 --- a/app/src/main/java/com/celzero/bravedns/database/CustomDomain.kt +++ b/app/src/main/java/com/celzero/bravedns/database/CustomDomain.kt @@ -35,11 +35,14 @@ class CustomDomain { override fun equals(other: Any?): Boolean { if (other !is CustomDomain) return false if (domain != other.domain) return false + if (status != other.status) return false return true } override fun hashCode(): Int { - return this.domain.hashCode() + var result = this.domain.hashCode() + result += result * 31 + this.status + return result } constructor(values: ContentValues?) { diff --git a/app/src/main/java/com/celzero/bravedns/database/CustomIp.kt b/app/src/main/java/com/celzero/bravedns/database/CustomIp.kt index 54ddf48ac..58bdbca93 100644 --- a/app/src/main/java/com/celzero/bravedns/database/CustomIp.kt +++ b/app/src/main/java/com/celzero/bravedns/database/CustomIp.kt @@ -48,12 +48,16 @@ class CustomIp { override fun equals(other: Any?): Boolean { if (other !is CustomIp) return false - return !(ipAddress != other.ipAddress && uid != other.uid) + if (ipAddress != other.ipAddress) return false + if (uid != other.uid) return false + if (status != other.status) return false + return true } override fun hashCode(): Int { var result = this.uid.hashCode() result += result * 31 + this.ipAddress.hashCode() + result += result * 31 + this.status return result } diff --git a/app/src/main/java/com/celzero/bravedns/database/ProxyApplicationMapping.kt b/app/src/main/java/com/celzero/bravedns/database/ProxyApplicationMapping.kt index fbd34dda8..1751d7466 100644 --- a/app/src/main/java/com/celzero/bravedns/database/ProxyApplicationMapping.kt +++ b/app/src/main/java/com/celzero/bravedns/database/ProxyApplicationMapping.kt @@ -29,11 +29,15 @@ class ProxyApplicationMapping { override fun equals(other: Any?): Boolean { if (other !is ProxyApplicationMapping) return false - return packageName == other.packageName + if (packageName != other.packageName) return false + if (uid != other.uid) return false + return true } override fun hashCode(): Int { - return this.packageName.hashCode() + var result = this.uid.hashCode() + result += result * 31 + this.packageName.hashCode() + return result } constructor( From 5f6eb160dd06daa9de64fdf2f548d14ef590a03b Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sun, 14 Jul 2024 19:20:33 +0530 Subject: [PATCH 073/188] ui: interchange netstat rx and tx values in home screen --- .../com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt index 53abbfdb9..40f5c8f6d 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt @@ -958,17 +958,18 @@ class HomeScreenFragment : Fragment(R.layout.fragment_home_screen) { b.fhsInternetSpeed.visibility = View.VISIBLE b.fhsInternetSpeedUnit.visibility = View.VISIBLE + // for netstack: rx: up, tx: down b.fhsInternetSpeed.text = getString( R.string.two_argument_space, getString( R.string.two_argument_space, - txBytes, + rxBytes, getString(R.string.symbol_black_up) ), getString( R.string.two_argument_space, - rxBytes, + txBytes, getString(R.string.symbol_black_down) ) ) From c70114280ff72799edbdfa0f65bbb3a0942471e5 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sun, 14 Jul 2024 19:21:01 +0530 Subject: [PATCH 074/188] string literal changes for v055o --- app/src/main/res/values/strings.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b66f972a4..dbffb6a89 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -100,17 +100,18 @@ IP Unselected notification - X Mastodon Matrix Reddit test + Rethink is experiencing low memory. System actions may be limited. %1$s 🔺 %1$s 🔻 %1$s + (%1$s) %1$s %2$s %1$s: %2$s %1$s / %2$s @@ -130,6 +131,8 @@ \u25b4 🔒 + KB/S + MB 1 24 @@ -633,9 +636,6 @@ Error creating pcap file, Try again! - Stablility improvement program - When app encounters an error, error logs are obtained and analysed to produce a solution. - Custom Probe IPs Add custom IPs to probe for network connectivity. @@ -790,14 +790,14 @@ https://docs.rethinkdns.com/ https://www.rethinkdns.com/faq https://github.com/celzero/rethink-app - https://twitter.com/rethinkdns + https://x.com/rethinkdns https://www.rethinkdns.com/ https://builders.mozilla.community/old/alumni.html https://fossunited.org/grants https://www.osomprivacy.com https://svc.rethinkdns.com/r/translate https://www.rethinkdns.com/privacy - https://joinmastodom.social + https://mastodon.social/@rdns https://www.reddit.com/r/rethinkdns https://matrix.to/#/!jrTSpJiEkFNNBMhSaE:matrix.org %1$s @@ -1556,8 +1556,8 @@ Anonymized DNS relay hosted in Singapore. - Application Log - The below logs contains your sensitive information. Please do not share it to any public platform. + App Log + Logs may contain sensitive information. From 841669a22cf56b0614d3ce1d31f5a46881ef1fe1 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sun, 14 Jul 2024 19:21:51 +0530 Subject: [PATCH 075/188] update version code and name for v055o --- app/src/main/AndroidManifest.xml | 4 ++-- gradle.properties | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 72b2de263..2a6e15781 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="46" + android:versionName="v055o"> diff --git a/gradle.properties b/gradle.properties index eaa9f3d65..653183c57 100644 --- a/gradle.properties +++ b/gradle.properties @@ -24,5 +24,5 @@ android.nonTransitiveRClass=true # Enable configuration cache org.gradle.unsafe.configuration-cache=true android.nonFinalResIds=true -# Version code for this module (45 for v055n) -VERSION_CODE=45 +# Version code for this module (46 for v055o) +VERSION_CODE=46 From e165be71d40656c42ca8b30fb13706da3303d9ee Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Tue, 16 Jul 2024 13:09:09 +0530 Subject: [PATCH 076/188] ui: improvements in recycler view's DIFF_CALLBACK --- .../celzero/bravedns/adapter/ConnectionTrackerAdapter.kt | 2 +- .../celzero/bravedns/adapter/LocalAdvancedViewAdapter.kt | 3 +-- .../celzero/bravedns/adapter/LocalSimpleViewAdapter.kt | 3 +-- .../com/celzero/bravedns/adapter/OneWgConfigAdapter.kt | 7 ++----- .../bravedns/adapter/RemoteAdvancedViewAdapter.kt | 3 +-- .../celzero/bravedns/adapter/RemoteSimpleViewAdapter.kt | 3 +-- .../com/celzero/bravedns/adapter/RethinkLogAdapter.kt | 2 +- .../java/com/celzero/bravedns/adapter/WgConfigAdapter.kt | 8 ++------ .../bravedns/ui/fragment/ConnectionTrackerFragment.kt | 4 +++- .../com/celzero/bravedns/database/RethinkLocalFileTag.kt | 1 + .../celzero/bravedns/database/RethinkRemoteFileTag.kt | 1 + .../java/com/celzero/bravedns/database/WgConfigFiles.kt | 9 ++++++++- 12 files changed, 23 insertions(+), 23 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/ConnectionTrackerAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/ConnectionTrackerAdapter.kt index 8799966b1..517ef94ff 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/ConnectionTrackerAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/ConnectionTrackerAdapter.kt @@ -69,7 +69,7 @@ class ConnectionTrackerAdapter(private val context: Context) : override fun areContentsTheSame( oldConnection: ConnectionTracker, newConnection: ConnectionTracker - ) = oldConnection.id == newConnection.id + ) = oldConnection == newConnection } private const val MAX_BYTES = 500000 // 500 KB diff --git a/app/src/full/java/com/celzero/bravedns/adapter/LocalAdvancedViewAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/LocalAdvancedViewAdapter.kt index bf9b51094..4818c3782 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/LocalAdvancedViewAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/LocalAdvancedViewAdapter.kt @@ -54,8 +54,7 @@ class LocalAdvancedViewAdapter(val context: Context) : oldConnection: RethinkLocalFileTag, newConnection: RethinkLocalFileTag ): Boolean { - return (oldConnection.value == newConnection.value && - oldConnection.isSelected == newConnection.isSelected) + return oldConnection == newConnection } } } diff --git a/app/src/full/java/com/celzero/bravedns/adapter/LocalSimpleViewAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/LocalSimpleViewAdapter.kt index e29a8fd63..5d4dea1e7 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/LocalSimpleViewAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/LocalSimpleViewAdapter.kt @@ -55,8 +55,7 @@ class LocalSimpleViewAdapter(val context: Context) : oldConnection: LocalBlocklistPacksMap, newConnection: LocalBlocklistPacksMap ): Boolean { - return (oldConnection.pack == newConnection.pack && - oldConnection.level == newConnection.level) + return oldConnection == newConnection } } } diff --git a/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt index 954d54b52..224821e91 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt @@ -73,17 +73,14 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns oldConnection: WgConfigFiles, newConnection: WgConfigFiles ): Boolean { - return (oldConnection == newConnection) + return oldConnection == newConnection } override fun areContentsTheSame( oldConnection: WgConfigFiles, newConnection: WgConfigFiles ): Boolean { - return (oldConnection.id == newConnection.id && - oldConnection.name == newConnection.name && - oldConnection.isActive == newConnection.isActive && - oldConnection.oneWireGuard == newConnection.oneWireGuard) + return oldConnection == newConnection } } } diff --git a/app/src/full/java/com/celzero/bravedns/adapter/RemoteAdvancedViewAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/RemoteAdvancedViewAdapter.kt index a42b2793e..5ae3b1965 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/RemoteAdvancedViewAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/RemoteAdvancedViewAdapter.kt @@ -55,8 +55,7 @@ class RemoteAdvancedViewAdapter(val context: Context) : oldConnection: RethinkRemoteFileTag, newConnection: RethinkRemoteFileTag ): Boolean { - return (oldConnection.value == newConnection.value && - oldConnection.isSelected == newConnection.isSelected) + return oldConnection == newConnection } } } diff --git a/app/src/full/java/com/celzero/bravedns/adapter/RemoteSimpleViewAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/RemoteSimpleViewAdapter.kt index 89cd84d28..c65b0d5e7 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/RemoteSimpleViewAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/RemoteSimpleViewAdapter.kt @@ -55,8 +55,7 @@ class RemoteSimpleViewAdapter(val context: Context) : oldConnection: RemoteBlocklistPacksMap, newConnection: RemoteBlocklistPacksMap ): Boolean { - return (oldConnection.pack == newConnection.pack && - oldConnection.level == newConnection.level) + return oldConnection == newConnection } } } diff --git a/app/src/full/java/com/celzero/bravedns/adapter/RethinkLogAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/RethinkLogAdapter.kt index 28731138f..60d6f449e 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/RethinkLogAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/RethinkLogAdapter.kt @@ -66,7 +66,7 @@ class RethinkLogAdapter(private val context: Context) : override fun areContentsTheSame( oldConnection: RethinkLog, newConnection: RethinkLog - ) = oldConnection.id == newConnection.id + ) = oldConnection == newConnection } private const val MAX_BYTES = 500000 // 500 KB diff --git a/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt index 23caf0baa..87c9977c5 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt @@ -66,18 +66,14 @@ class WgConfigAdapter(private val context: Context) : oldConnection: WgConfigFiles, newConnection: WgConfigFiles ): Boolean { - return (oldConnection == newConnection) + return oldConnection == newConnection } override fun areContentsTheSame( oldConnection: WgConfigFiles, newConnection: WgConfigFiles ): Boolean { - return (oldConnection.id == newConnection.id && - oldConnection.name == newConnection.name && - oldConnection.isActive == newConnection.isActive && - oldConnection.isCatchAll == newConnection.isCatchAll && - oldConnection.isLockdown == newConnection.isLockdown) + return oldConnection == newConnection } } } diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/ConnectionTrackerFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/ConnectionTrackerFragment.kt index 39faebe96..9c93dc6a1 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/ConnectionTrackerFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/ConnectionTrackerFragment.kt @@ -63,6 +63,7 @@ class ConnectionTrackerFragment : companion object { const val PROTOCOL_FILTER_PREFIX = "P:" + private const val QUERY_TEXT_TIMEOUT: Long = 600 fun newInstance(param: String): ConnectionTrackerFragment { val args = Bundle() @@ -105,6 +106,7 @@ class ConnectionTrackerFragment : layoutManager = LinearLayoutManager(requireContext()) b.recyclerConnection.layoutManager = layoutManager val recyclerAdapter = ConnectionTrackerAdapter(requireContext()) + recyclerAdapter.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY viewLifecycleOwner.lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.connectionTrackerList.observe(viewLifecycleOwner) { it -> @@ -266,7 +268,7 @@ class ConnectionTrackerFragment : } override fun onQueryTextChange(query: String): Boolean { - Utilities.delay(500, lifecycleScope) { + Utilities.delay(QUERY_TEXT_TIMEOUT, lifecycleScope) { if (this.isAdded) { this.filterQuery = query viewModel.setFilter(query, filterCategories, filterType) diff --git a/app/src/main/java/com/celzero/bravedns/database/RethinkLocalFileTag.kt b/app/src/main/java/com/celzero/bravedns/database/RethinkLocalFileTag.kt index a03df18fa..d77e1f0a6 100644 --- a/app/src/main/java/com/celzero/bravedns/database/RethinkLocalFileTag.kt +++ b/app/src/main/java/com/celzero/bravedns/database/RethinkLocalFileTag.kt @@ -41,6 +41,7 @@ class RethinkLocalFileTag { override fun equals(other: Any?): Boolean { if (other !is RethinkLocalFileTag) return false if (value != other.value) return false + if (isSelected != other.isSelected) return false return true } diff --git a/app/src/main/java/com/celzero/bravedns/database/RethinkRemoteFileTag.kt b/app/src/main/java/com/celzero/bravedns/database/RethinkRemoteFileTag.kt index 64a6b8974..0c9367a78 100644 --- a/app/src/main/java/com/celzero/bravedns/database/RethinkRemoteFileTag.kt +++ b/app/src/main/java/com/celzero/bravedns/database/RethinkRemoteFileTag.kt @@ -40,6 +40,7 @@ class RethinkRemoteFileTag { override fun equals(other: Any?): Boolean { if (other !is RethinkRemoteFileTag) return false if (value != other.value) return false + if (isSelected != other.isSelected) return false return true } diff --git a/app/src/main/java/com/celzero/bravedns/database/WgConfigFiles.kt b/app/src/main/java/com/celzero/bravedns/database/WgConfigFiles.kt index 507de02fa..698dc72b4 100644 --- a/app/src/main/java/com/celzero/bravedns/database/WgConfigFiles.kt +++ b/app/src/main/java/com/celzero/bravedns/database/WgConfigFiles.kt @@ -46,11 +46,18 @@ class WgConfigFiles { override fun equals(other: Any?): Boolean { if (other !is WgConfigFiles) return false if (id != other.id) return false + if (name != other.name) return false + if (isActive != other.isActive) return false + if (isCatchAll != other.isCatchAll) return false + if (oneWireGuard != other.oneWireGuard) return false + if (isLockdown != other.isLockdown) return false return true } override fun hashCode(): Int { - return this.id.hashCode() + var result = this.id.hashCode() + result += result * 31 + this.name.hashCode() + return result } @Ignore From 4b6d6068752d722dd59a2d1e38d197b5c7610afd Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 18 Jul 2024 17:34:25 +0530 Subject: [PATCH 077/188] Fix: #1608, access database with io --- app/src/full/java/com/celzero/bravedns/ui/HomeScreenActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/HomeScreenActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/HomeScreenActivity.kt index d3ca56736..ac0db1e15 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/HomeScreenActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/HomeScreenActivity.kt @@ -396,7 +396,7 @@ class HomeScreenActivity : AppCompatActivity(R.layout.activity_home_screen) { // to SystemClock.elapsedRealtime persistentState.biometricAuthTime = SystemClock.elapsedRealtime() // set the rethink app in firewall mode as allowed by default - appInfoDb.resetRethinkAppFirewallMode() + io { appInfoDb.resetRethinkAppFirewallMode() } } // fixme: find a cleaner way to implement this, move this to some other place From 01382d1eaba6e14a9c04e26c6fe56a096a597218 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 18 Jul 2024 17:35:22 +0530 Subject: [PATCH 078/188] replace first with firstOrNull() --- .../celzero/bravedns/ui/activity/WgConfigDetailActivity.kt | 2 +- .../com/celzero/bravedns/service/RethinkBlocklistManager.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt index 4df8762e0..d2eb1d185 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt @@ -85,7 +85,7 @@ class WgConfigDetailActivity : AppCompatActivity(R.layout.activity_wg_detail) { fun isDefault() = this == DEFAULT companion object { - fun fromInt(value: Int) = entries.first { it.value == value } + fun fromInt(value: Int) = entries.firstOrNull { it.value == value } ?: DEFAULT } } diff --git a/app/src/main/java/com/celzero/bravedns/service/RethinkBlocklistManager.kt b/app/src/main/java/com/celzero/bravedns/service/RethinkBlocklistManager.kt index 348c718d4..5b17b1574 100644 --- a/app/src/main/java/com/celzero/bravedns/service/RethinkBlocklistManager.kt +++ b/app/src/main/java/com/celzero/bravedns/service/RethinkBlocklistManager.kt @@ -182,7 +182,7 @@ object RethinkBlocklistManager : KoinComponent { key.pack, key.level, packsBlocklistMapping.get(key).toList(), - dbFileTagLocal.first { it.pack?.contains(key.pack) == true }.group + dbFileTagLocal.firstOrNull { it.pack?.contains(key.pack) == true }?.group ?: "" ) } ) @@ -266,7 +266,7 @@ object RethinkBlocklistManager : KoinComponent { key.pack, key.level, packsBlocklistMapping.get(key).toList(), - dbFileTagRemote.first { it.pack?.contains(key.pack) == true }.group + dbFileTagRemote.firstOrNull { it.pack?.contains(key.pack) == true }?.group ?: "" ) } ) From 1c936d36f7f0ce0ddd7c7da2a700d47399d53e2e Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 18 Jul 2024 17:37:20 +0530 Subject: [PATCH 079/188] repository methods to suspend functions, despite being called from IO --- .../com/celzero/bravedns/database/AppInfoRepository.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/celzero/bravedns/database/AppInfoRepository.kt b/app/src/main/java/com/celzero/bravedns/database/AppInfoRepository.kt index 437725fc0..7daff09ba 100644 --- a/app/src/main/java/com/celzero/bravedns/database/AppInfoRepository.kt +++ b/app/src/main/java/com/celzero/bravedns/database/AppInfoRepository.kt @@ -86,23 +86,23 @@ class AppInfoRepository(private val appInfoDAO: AppInfoDAO) { return appInfoDAO.deleteByUid(uid) } - fun getDataUsageByUid(uid: Int): DataUsage { + suspend fun getDataUsageByUid(uid: Int): DataUsage { return appInfoDAO.getDataUsageByUid(uid) } - fun updateDataUsageByUid(uid: Int, uploadBytes: Long, downloadBytes: Long) { + suspend fun updateDataUsageByUid(uid: Int, uploadBytes: Long, downloadBytes: Long) { appInfoDAO.updateDataUsageByUid(uid, uploadBytes, downloadBytes) } - fun updateProxyExcluded(uid: Int, isProxyExcluded: Boolean) { + suspend fun updateProxyExcluded(uid: Int, isProxyExcluded: Boolean) { appInfoDAO.updateProxyExcluded(uid, isProxyExcluded) } - fun resetRethinkAppFirewallMode() { + suspend fun resetRethinkAppFirewallMode() { appInfoDAO.resetRethinkAppFirewallMode() } - fun getAppInfoUidForPackageName(packageName: String): Int { + suspend fun getAppInfoUidForPackageName(packageName: String): Int { return appInfoDAO.getAppInfoUidForPackageName(packageName) } } From 01113fa732d63edc13e2232afa0354a6399393bf Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 18 Jul 2024 17:56:59 +0530 Subject: [PATCH 080/188] ui: show toast in wg config if vpn is not active --- .../com/celzero/bravedns/adapter/OneWgConfigAdapter.kt | 9 +++++++++ .../java/com/celzero/bravedns/adapter/WgConfigAdapter.kt | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt index 224821e91..c6caa7b88 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt @@ -449,6 +449,15 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns } private fun launchConfigDetail(id: Int) { + if (!VpnController.hasTunnel()) { + Utilities.showToastUiCentered( + context, + context.getString(R.string.ssv_toast_start_rethink), + Toast.LENGTH_SHORT + ) + return + } + val intent = Intent(context, WgConfigDetailActivity::class.java) intent.putExtra(INTENT_EXTRA_WG_ID, id) intent.putExtra(INTENT_EXTRA_WG_TYPE, WgConfigDetailActivity.WgType.ONE_WG.value) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt index 87c9977c5..09bae1316 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt @@ -521,6 +521,15 @@ class WgConfigAdapter(private val context: Context) : } private fun launchConfigDetail(id: Int) { + if (!VpnController.hasTunnel()) { + Utilities.showToastUiCentered( + context, + context.getString(R.string.ssv_toast_start_rethink), + Toast.LENGTH_SHORT + ) + return + } + val intent = Intent(context, WgConfigDetailActivity::class.java) intent.putExtra(INTENT_EXTRA_WG_ID, id) intent.putExtra( From 59cd90c1ed3439f520fa4b2616eea3d6f03825dd Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 18 Jul 2024 18:10:50 +0530 Subject: [PATCH 081/188] ui: rmv app name color change for sys and user apps setting the app name with different color for system and user apps causes conflict with the firewall status like blocked and isolated so removing the color change for now. --- .../bravedns/adapter/FirewallAppListAdapter.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt index ecf1fe1d7..3765cf2af 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt @@ -57,8 +57,8 @@ class FirewallAppListAdapter( ) : PagingDataAdapter(DIFF_CALLBACK) { private val packageManager: PackageManager = context.packageManager - private val systemAppColor: Int by lazy { UIUtils.fetchColor(context, R.attr.textColorAccentBad) } - private val userAppColor: Int by lazy { UIUtils.fetchColor(context, R.attr.primaryTextColor) } + // private val systemAppColor: Int by lazy { UIUtils.fetchColor(context, R.attr.textColorAccentBad) } + // private val userAppColor: Int by lazy { UIUtils.fetchColor(context, R.attr.primaryTextColor) } companion object { private val DIFF_CALLBACK = @@ -104,11 +104,15 @@ class FirewallAppListAdapter( val connStatus = FirewallManager.connectionStatus(appInfo.uid) uiCtx { b.firewallAppLabelTv.text = appInfo.appName - if (appInfo.isSystemApp) { + // setting the appname with different color for system and user apps + // causes conflict with the firewall status like blocked and isolated + // so removing the color change for now + /* if (appInfo.isSystemApp) { b.firewallAppLabelTv.setTextColor(systemAppColor) } else { b.firewallAppLabelTv.setTextColor(userAppColor) - } + } */ + // set the alpha based on internet permission if (appInfo.hasInternetPermission(packageManager)) { b.firewallAppLabelTv.alpha = 1f } else { From 35e43fa9a85d65060f881e1c685ee86079d086e3 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Fri, 19 Jul 2024 11:36:27 +0530 Subject: [PATCH 082/188] Fix 1536, update proper desc for adguard DoT --- .../com/celzero/bravedns/database/AppDatabase.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/celzero/bravedns/database/AppDatabase.kt b/app/src/main/java/com/celzero/bravedns/database/AppDatabase.kt index 0862db0d4..dbd4cc9f9 100644 --- a/app/src/main/java/com/celzero/bravedns/database/AppDatabase.kt +++ b/app/src/main/java/com/celzero/bravedns/database/AppDatabase.kt @@ -55,7 +55,7 @@ import java.io.File DoTEndpoint::class, ODoHEndpoint::class ], - version = 23, + version = 24, exportSchema = false ) @TypeConverters(Converters::class) @@ -99,6 +99,7 @@ abstract class AppDatabase : RoomDatabase() { .addMigrations(MIGRATION_20_21) .addMigrations(MIGRATION_21_22) .addMigrations(MIGRATION_22_23) + .addMigrations(MIGRATION_23_24) .build() private val roomCallback: Callback = @@ -971,6 +972,15 @@ abstract class AppDatabase : RoomDatabase() { } } + private val MIGRATION_23_24: Migration = + object : Migration(23, 24) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL( + "UPDATE DoTEndpoint set desc = 'Adguard DNS over TLS. Blocks ads, tracking, and phishing.' where name = 'Adguard' and id = 2" + ) + } + } + // ref: stackoverflow.com/a/57204285 private fun doesColumnExistInTable( db: SupportSQLiteDatabase, From 6427b2ff2ce81edc328028b3e1f6e8de8180c9ea Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Fri, 19 Jul 2024 20:20:51 +0530 Subject: [PATCH 083/188] ui: #1516 filter for partly blocked apps --- .../bravedns/ui/activity/AppListActivity.kt | 37 ++++++++++++++++--- 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/AppListActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/AppListActivity.kt index c9d30197d..dc82917d8 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/AppListActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/AppListActivity.kt @@ -20,8 +20,6 @@ import android.content.res.Configuration import android.graphics.PorterDuff import android.graphics.PorterDuffColorFilter import android.os.Bundle -import android.os.Handler -import android.os.Looper import android.view.LayoutInflater import android.view.View import android.view.animation.Animation @@ -118,14 +116,18 @@ class AppListActivity : ALL(0), ALLOWED(1), BLOCKED(2), - BYPASS(3), - EXCLUDED(4), - LOCKDOWN(5); + BLOCKED_WIFI(3), + BLOCKED_MOBILE_DATA(4), + BYPASS(5), + EXCLUDED(6), + LOCKDOWN(7); fun getFilter(): Set { return when (this) { ALL -> setOf(0, 1, 2, 3, 4, 5, 7) ALLOWED -> setOf(5) + BLOCKED_WIFI -> setOf(5) + BLOCKED_MOBILE_DATA -> setOf(5) BLOCKED -> setOf(5) BYPASS -> setOf(2, 7) EXCLUDED -> setOf(3) @@ -137,7 +139,9 @@ class AppListActivity : return when (this) { ALL -> setOf(0, 1, 2, 3) ALLOWED -> setOf(3) - BLOCKED -> setOf(0, 1, 2) + BLOCKED_WIFI -> setOf(1) + BLOCKED_MOBILE_DATA -> setOf(2) + BLOCKED -> setOf(0) BYPASS -> setOf(0, 1, 2, 3) EXCLUDED -> setOf(0, 1, 2, 3) LOCKDOWN -> setOf(0, 1, 2, 3) @@ -148,6 +152,8 @@ class AppListActivity : return when (this) { ALL -> context.getString(R.string.lbl_all) ALLOWED -> context.getString(R.string.lbl_allowed) + BLOCKED_WIFI -> context.getString(R.string.two_argument_colon, context.getString(R.string.lbl_blocked), context.getString(R.string.firewall_rule_block_unmetered)) + BLOCKED_MOBILE_DATA -> context.getString(R.string.two_argument_colon, context.getString(R.string.lbl_blocked), context.getString(R.string.firewall_rule_block_metered)) BLOCKED -> context.getString(R.string.lbl_blocked) BYPASS -> context.getString(R.string.fapps_firewall_filter_bypass_universal) EXCLUDED -> context.getString(R.string.fapps_firewall_filter_excluded) @@ -160,6 +166,8 @@ class AppListActivity : return when (id) { ALL.id -> ALL ALLOWED.id -> ALLOWED + BLOCKED_WIFI.id -> BLOCKED_WIFI + BLOCKED_MOBILE_DATA.id -> BLOCKED_MOBILE_DATA BLOCKED.id -> BLOCKED BYPASS.id -> BYPASS EXCLUDED.id -> EXCLUDED @@ -491,6 +499,21 @@ class AppListActivity : makeFirewallChip(FirewallFilter.ALLOWED.id, getString(R.string.lbl_allowed), false) val blocked = makeFirewallChip(FirewallFilter.BLOCKED.id, getString(R.string.lbl_blocked), false) + val blockedWifiTxt = getString( + R.string.two_argument_colon, + getString(R.string.lbl_blocked), + getString(R.string.firewall_rule_block_unmetered) + ) + val blockedWifi = + makeFirewallChip(FirewallFilter.BLOCKED_WIFI.id, blockedWifiTxt, false) + val blockedMobileDataTxt = getString( + R.string.two_argument_colon, + getString(R.string.lbl_blocked), + getString(R.string.firewall_rule_block_metered) + ) + val blockedMobileData = + makeFirewallChip(FirewallFilter.BLOCKED_MOBILE_DATA.id, blockedMobileDataTxt, false) + val bypassUniversal = makeFirewallChip( FirewallFilter.BYPASS.id, @@ -510,6 +533,8 @@ class AppListActivity : b.ffaFirewallChipGroup.addView(none) b.ffaFirewallChipGroup.addView(allowed) b.ffaFirewallChipGroup.addView(blocked) + b.ffaFirewallChipGroup.addView(blockedWifi) + b.ffaFirewallChipGroup.addView(blockedMobileData) b.ffaFirewallChipGroup.addView(bypassUniversal) b.ffaFirewallChipGroup.addView(excluded) b.ffaFirewallChipGroup.addView(lockdown) From c7384d8e4f6c94d0642ecd8b467baba3568f18ea Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 20 Jul 2024 14:01:40 +0530 Subject: [PATCH 084/188] ui: option to delete multiple ips from custom ip screen --- .../bravedns/adapter/CustomIpAdapter.kt | 73 ++++++++++++++++++- .../bravedns/ui/fragment/CustomIpFragment.kt | 24 ++++-- .../res/layout/list_item_custom_all_ip.xml | 13 +++- .../full/res/layout/list_item_custom_ip.xml | 12 ++- .../bravedns/database/CustomIpRepository.kt | 4 + .../bravedns/service/IpRulesManager.kt | 14 ++++ 6 files changed, 127 insertions(+), 13 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/CustomIpAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/CustomIpAdapter.kt index de8301a03..47886b45f 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/CustomIpAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/CustomIpAdapter.kt @@ -26,6 +26,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.WindowManager +import android.widget.CheckBox import android.widget.ImageView import android.widget.Toast import androidx.core.view.isVisible @@ -63,6 +64,9 @@ import kotlinx.coroutines.withContext class CustomIpAdapter(private val context: Context, private val type: CustomRulesActivity.RULES) : PagingDataAdapter(DIFF_CALLBACK) { + private val selectedItems = mutableSetOf() + private var isSelectionMode = false + companion object { private val DIFF_CALLBACK = object : DiffUtil.ItemCallback() { @@ -97,9 +101,11 @@ class CustomIpAdapter(private val context: Context, private val type: CustomRule override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val customIp: CustomIp = getItem(position) ?: return + when (holder) { is CustomIpAdapter.CustomIpsViewHolderWithHeader -> { holder.update(customIp) + } is CustomIpAdapter.CustomIpsViewHolderWithoutHeader -> { holder.update(customIp) @@ -126,6 +132,14 @@ class CustomIpAdapter(private val context: Context, private val type: CustomRule } } + fun getSelectedItems(): List = selectedItems.toList() + + fun clearSelection() { + selectedItems.clear() + isSelectionMode = false + notifyDataSetChanged() + } + private fun displayIcon(drawable: Drawable?, mIconImageView: ImageView) { Glide.with(context) .load(drawable) @@ -234,6 +248,10 @@ class CustomIpAdapter(private val context: Context, private val type: CustomRule private lateinit var customIp: CustomIp fun update(ci: CustomIp) { + customIp = ci + + b.customIpCheckbox.isChecked = selectedItems.contains(customIp) + b.customIpCheckbox.visibility = if (isSelectionMode) View.VISIBLE else View.GONE io { val appNames = FirewallManager.getAppNamesByUid(ci.uid) val appName = getAppName(ci.uid, appNames) @@ -251,7 +269,7 @@ class CustomIpAdapter(private val context: Context, private val type: CustomRule } } - customIp = ci + b.customIpLabelTv.text = context.getString( R.string.ci_ip_label, @@ -278,9 +296,32 @@ class CustomIpAdapter(private val context: Context, private val type: CustomRule b.customIpExpandIcon.setOnClickListener { toggleActionsUi() } - b.customIpContainer.setOnClickListener { toggleActionsUi() } + b.customIpContainer.setOnClickListener { + if (isSelectionMode) { + toggleSelection(customIp) + } else { + toggleActionsUi() + } + } b.customIpSeeMoreChip.setOnClickListener { openAppWiseRulesActivity(customIp.uid) } + + b.customIpContainer.setOnLongClickListener { + isSelectionMode = true + selectedItems.add(customIp) + notifyDataSetChanged() + true + } + } + + private fun toggleSelection(item: CustomIp) { + if (selectedItems.contains(item)) { + selectedItems.remove(item) + b.customIpCheckbox.isChecked = false + } else { + selectedItems.add(item) + b.customIpCheckbox.isChecked = true + } } private fun openAppWiseRulesActivity(uid: Int) { @@ -514,6 +555,9 @@ class CustomIpAdapter(private val context: Context, private val type: CustomRule fun update(ci: CustomIp) { customIp = ci + b.customIpCheckbox.isChecked = selectedItems.contains(customIp) + b.customIpCheckbox.visibility = if (isSelectionMode) View.VISIBLE else View.GONE + b.customIpLabelTv.text = context.getString( R.string.ci_ip_label, @@ -540,7 +584,30 @@ class CustomIpAdapter(private val context: Context, private val type: CustomRule b.customIpExpandIcon.setOnClickListener { toggleActionsUi() } - b.customIpContainer.setOnClickListener { toggleActionsUi() } + b.customIpContainer.setOnClickListener { + if (isSelectionMode) { + toggleSelection(customIp) + } else { + toggleActionsUi() + } + } + + b.customIpContainer.setOnLongClickListener { + isSelectionMode = true + selectedItems.add(customIp) + notifyDataSetChanged() + true + } + } + + private fun toggleSelection(item: CustomIp) { + if (selectedItems.contains(item)) { + selectedItems.remove(item) + b.customIpCheckbox.isChecked = false + } else { + selectedItems.add(item) + b.customIpCheckbox.isChecked = true + } } private fun showBypassUi(uid: Int) { diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/CustomIpFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/CustomIpFragment.kt index d65c3f1f7..5b2cadc3d 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/CustomIpFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/CustomIpFragment.kt @@ -51,6 +51,7 @@ class CustomIpFragment : Fragment(R.layout.fragment_custom_ip), SearchView.OnQue private val viewModel: CustomIpViewModel by viewModel() private var uid = UID_EVERYBODY private var rules = CustomRulesActivity.RULES.APP_SPECIFIC_RULES + private lateinit var adapter: CustomIpAdapter companion object { fun newInstance(uid: Int, rules: CustomRulesActivity.RULES): CustomIpFragment { @@ -148,8 +149,7 @@ class CustomIpFragment : Fragment(R.layout.fragment_custom_ip), SearchView.OnQue private fun setupAdapterForApp() { observeAppSpecificRules() - val adapter = - CustomIpAdapter(requireContext(), CustomRulesActivity.RULES.APP_SPECIFIC_RULES) + adapter = CustomIpAdapter(requireContext(), CustomRulesActivity.RULES.APP_SPECIFIC_RULES) viewModel.setUid(uid) viewModel.customIpDetails.observe(viewLifecycleOwner) { adapter.submitData(this.lifecycle, it) @@ -159,7 +159,7 @@ class CustomIpFragment : Fragment(R.layout.fragment_custom_ip), SearchView.OnQue private fun setupAdapterForAllApps() { observeAllAppsRules() - val adapter = CustomIpAdapter(requireContext(), CustomRulesActivity.RULES.ALL_RULES) + adapter = CustomIpAdapter(requireContext(), CustomRulesActivity.RULES.ALL_RULES) viewModel.allIpRules.observe(viewLifecycleOwner) { adapter.submitData(this.lifecycle, it) } b.cipRecycler.adapter = adapter } @@ -265,10 +265,16 @@ class CustomIpFragment : Fragment(R.layout.fragment_custom_ip), SearchView.OnQue builder.setMessage(R.string.univ_delete_firewall_dialog_message) builder.setPositiveButton(getString(R.string.univ_ip_delete_dialog_positive)) { _, _ -> io { - if (rules == CustomRulesActivity.RULES.APP_SPECIFIC_RULES) { - IpRulesManager.deleteRulesByUid(uid) + val selectedItems = adapter.getSelectedItems() + if (selectedItems.isNotEmpty()) { + IpRulesManager.deleteRules(selectedItems) + uiCtx { adapter.clearSelection() } } else { - IpRulesManager.deleteAllAppsRules() + if (rules == CustomRulesActivity.RULES.APP_SPECIFIC_RULES) { + IpRulesManager.deleteRulesByUid(uid) + } else { + IpRulesManager.deleteAllAppsRules() + } } } Utilities.showToastUiCentered( @@ -279,7 +285,7 @@ class CustomIpFragment : Fragment(R.layout.fragment_custom_ip), SearchView.OnQue } builder.setNegativeButton(getString(R.string.lbl_cancel)) { _, _ -> - // no-op + adapter.clearSelection() } builder.setCancelable(true) @@ -290,6 +296,10 @@ class CustomIpFragment : Fragment(R.layout.fragment_custom_ip), SearchView.OnQue withContext(Dispatchers.IO) { f() } } + private suspend fun uiCtx(f: suspend () -> Unit) { + withContext(Dispatchers.Main) { f() } + } + private fun io(f: suspend () -> Unit) { lifecycleScope.launch(Dispatchers.IO) { f() } } diff --git a/app/src/full/res/layout/list_item_custom_all_ip.xml b/app/src/full/res/layout/list_item_custom_all_ip.xml index 3ca80c255..d9780ba6a 100644 --- a/app/src/full/res/layout/list_item_custom_all_ip.xml +++ b/app/src/full/res/layout/list_item_custom_all_ip.xml @@ -62,14 +62,23 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_below="@id/custom_ip_app_name_container" - android:layout_margin="5dp" android:padding="5dp"> + + + + ) { + list.forEach { customIpDao.delete(it) } + } } diff --git a/app/src/main/java/com/celzero/bravedns/service/IpRulesManager.kt b/app/src/main/java/com/celzero/bravedns/service/IpRulesManager.kt index 3866ea0c5..15da140a9 100644 --- a/app/src/main/java/com/celzero/bravedns/service/IpRulesManager.kt +++ b/app/src/main/java/com/celzero/bravedns/service/IpRulesManager.kt @@ -320,6 +320,20 @@ object IpRulesManager : KoinComponent { resultsCache.invalidateAll() } + suspend fun deleteRules(list: List) { + list.forEach { + val pair = it.getCustomIpAddress() + val ipaddr = pair.first + val port = pair.second + val k = normalize(ipaddr) + if (!k.isNullOrEmpty()) { + iptree.esc(k, treeVal(it.uid, port, it.status)) + } + } + db.deleteRules(list) + resultsCache.invalidateAll() + } + suspend fun deleteAllAppsRules() { db.deleteAllAppsRules() iptree.clear() From 718729a300992e4c840448bd538099f3ccde4fac Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 20 Jul 2024 14:11:04 +0530 Subject: [PATCH 085/188] ui: option to delete multiple ips from custom ip --- app/src/main/java/com/celzero/bravedns/database/CustomIpDao.kt | 2 ++ .../java/com/celzero/bravedns/database/CustomIpRepository.kt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/celzero/bravedns/database/CustomIpDao.kt b/app/src/main/java/com/celzero/bravedns/database/CustomIpDao.kt index 6db932286..051cb04cc 100644 --- a/app/src/main/java/com/celzero/bravedns/database/CustomIpDao.kt +++ b/app/src/main/java/com/celzero/bravedns/database/CustomIpDao.kt @@ -36,6 +36,8 @@ interface CustomIpDao { @Delete fun delete(customIp: CustomIp) + @Delete fun deleteAll(customIp: List) + @Transaction @Query("select * from CustomIp order by uid") fun getCustomIpRules(): List diff --git a/app/src/main/java/com/celzero/bravedns/database/CustomIpRepository.kt b/app/src/main/java/com/celzero/bravedns/database/CustomIpRepository.kt index 1eef95dec..9b6130768 100644 --- a/app/src/main/java/com/celzero/bravedns/database/CustomIpRepository.kt +++ b/app/src/main/java/com/celzero/bravedns/database/CustomIpRepository.kt @@ -60,6 +60,6 @@ class CustomIpRepository(private val customIpDao: CustomIpDao) { } suspend fun deleteRules(list: List) { - list.forEach { customIpDao.delete(it) } + customIpDao.deleteAll(list) } } From 1bc985b4ae4e9064a035da379b8135719a344702 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 22 Jul 2024 15:48:45 +0530 Subject: [PATCH 086/188] ui: option to delete multiple ips from custom domain --- .../bravedns/adapter/CustomDomainAdapter.kt | 77 +++++++++++++++++-- .../ui/fragment/CustomDomainFragment.kt | 24 ++++-- .../layout/list_item_custom_all_domain.xml | 12 ++- .../res/layout/list_item_custom_domain.xml | 12 ++- .../bravedns/database/CustomDomainDAO.kt | 2 + .../database/CustomDomainRepository.kt | 4 + .../bravedns/service/DomainRulesManager.kt | 8 ++ 7 files changed, 122 insertions(+), 17 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/CustomDomainAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/CustomDomainAdapter.kt index b3f72cbff..e3dee1908 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/CustomDomainAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/CustomDomainAdapter.kt @@ -61,6 +61,9 @@ import kotlinx.coroutines.withContext class CustomDomainAdapter(val context: Context, val rule: CustomRulesActivity.RULES) : PagingDataAdapter(DIFF_CALLBACK) { + private val selectedItems = mutableSetOf() + private var isSelectionMode = false + companion object { private val DIFF_CALLBACK = @@ -116,6 +119,14 @@ class CustomDomainAdapter(val context: Context, val rule: CustomRulesActivity.RU } } + fun getSelectedItems(): List = selectedItems.toList() + + fun clearSelection() { + selectedItems.clear() + isSelectionMode = false + notifyDataSetChanged() + } + override fun getItemViewType(position: Int): Int { if (rule == CustomRulesActivity.RULES.APP_SPECIFIC_RULES) { return R.layout.list_item_custom_domain @@ -385,6 +396,10 @@ class CustomDomainAdapter(val context: Context, val rule: CustomRulesActivity.RU private lateinit var customDomain: CustomDomain fun update(cd: CustomDomain) { + this.customDomain = cd + + b.customDomainCheckbox.isChecked = selectedItems.contains(cd) + b.customDomainCheckbox.visibility = if (isSelectionMode) View.VISIBLE else View.GONE io { val appInfo = FirewallManager.getAppInfoByUid(cd.uid) val appNames = FirewallManager.getAppNamesByUid(cd.uid) @@ -401,33 +416,56 @@ class CustomDomainAdapter(val context: Context, val rule: CustomRulesActivity.RU b.customDomainAppIconIv ) - this.customDomain = cd + b.customDomainLabelTv.text = customDomain.domain b.customDomainToggleGroup.tag = 1 // update toggle group button based on the status - updateToggleGroup(customDomain.status) + updateToggleGroup(cd.status) // whether to show the toggle group or not toggleActionsUi() // update status in desc and status flag (N/B/W) updateStatusUi( - DomainRulesManager.Status.getStatus(customDomain.status), - customDomain.modifiedTs + DomainRulesManager.Status.getStatus(cd.status), + cd.modifiedTs ) b.customDomainToggleGroup.addOnButtonCheckedListener(domainRulesGroupListener) - b.customDomainEditIcon.setOnClickListener { showEditDomainDialog(customDomain) } + b.customDomainEditIcon.setOnClickListener { showEditDomainDialog(cd) } b.customDomainExpandIcon.setOnClickListener { toggleActionsUi() } - b.customDomainContainer.setOnClickListener { toggleActionsUi() } + b.customDomainContainer.setOnClickListener { + if (isSelectionMode) { + toggleSelection(cd) + } else { + toggleActionsUi() + } + } b.customDomainSeeMoreChip.setOnClickListener { openAppWiseRulesActivity(cd.uid) } + + b.customDomainContainer.setOnLongClickListener { + isSelectionMode = true + selectedItems.add(cd) + notifyDataSetChanged() + true + } } } } + private fun toggleSelection(item: CustomDomain) { + if (selectedItems.contains(item)) { + selectedItems.remove(item) + b.customDomainCheckbox.isChecked = false + } else { + selectedItems.add(item) + b.customDomainCheckbox.isChecked = true + } + } + private fun openAppWiseRulesActivity(uid: Int) { val intent = Intent(context, CustomRulesActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED @@ -596,6 +634,8 @@ class CustomDomainAdapter(val context: Context, val rule: CustomRulesActivity.RU fun update(cd: CustomDomain) { this.customDomain = cd + b.customDomainCheckbox.isChecked = selectedItems.contains(cd) + b.customDomainCheckbox.visibility = if (isSelectionMode) View.VISIBLE else View.GONE b.customDomainLabelTv.text = customDomain.domain b.customDomainToggleGroup.tag = 1 @@ -615,7 +655,30 @@ class CustomDomainAdapter(val context: Context, val rule: CustomRulesActivity.RU b.customDomainExpandIcon.setOnClickListener { toggleActionsUi() } - b.customDomainContainer.setOnClickListener { toggleActionsUi() } + b.customDomainContainer.setOnClickListener { + if (isSelectionMode) { + toggleSelection(cd) + } else { + toggleActionsUi() + } + } + + b.customDomainContainer.setOnLongClickListener { + isSelectionMode = true + selectedItems.add(cd) + notifyDataSetChanged() + true + } + } + + private fun toggleSelection(item: CustomDomain) { + if (selectedItems.contains(item)) { + selectedItems.remove(item) + b.customDomainCheckbox.isChecked = false + } else { + selectedItems.add(item) + b.customDomainCheckbox.isChecked = true + } } private fun updateToggleGroup(id: Int) { diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/CustomDomainFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/CustomDomainFragment.kt index 5576ed3b0..5f81aa12a 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/CustomDomainFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/CustomDomainFragment.kt @@ -50,6 +50,7 @@ class CustomDomainFragment : private val b by viewBinding(FragmentCustomDomainBinding::bind) private var layoutManager: RecyclerView.LayoutManager? = null + private lateinit var adapter: CustomDomainAdapter private val viewModel by inject() @@ -101,7 +102,7 @@ class CustomDomainFragment : private fun setupAppSpecificRules(rule: CustomRulesActivity.RULES) { observeCustomRules() - val adapter = CustomDomainAdapter(requireContext(), rule) + adapter = CustomDomainAdapter(requireContext(), rule) b.cdaRecycler.adapter = adapter viewModel.setUid(uid) viewModel.customDomains.observe(this as LifecycleOwner) { @@ -111,7 +112,7 @@ class CustomDomainFragment : private fun setupAllRules(rule: CustomRulesActivity.RULES) { observeAllRules() - val adapter = CustomDomainAdapter(requireContext(), rule) + adapter = CustomDomainAdapter(requireContext(), rule) b.cdaRecycler.adapter = adapter viewModel.allDomainRules.observe(this as LifecycleOwner) { adapter.submitData(this.lifecycle, it) @@ -300,11 +301,18 @@ class CustomDomainFragment : builder.setTitle(R.string.univ_delete_firewall_dialog_title) builder.setMessage(R.string.univ_delete_firewall_dialog_message) builder.setPositiveButton(getString(R.string.univ_ip_delete_dialog_positive)) { _, _ -> + io { - if (rule == CustomRulesActivity.RULES.APP_SPECIFIC_RULES) { - DomainRulesManager.deleteRulesByUid(uid) + val selectedItems = adapter.getSelectedItems() + if (selectedItems.isNotEmpty()) { + uiCtx { adapter.clearSelection() } + DomainRulesManager.deleteRules(selectedItems) } else { - DomainRulesManager.deleteAllRules() + if (rule == CustomRulesActivity.RULES.APP_SPECIFIC_RULES) { + DomainRulesManager.deleteRulesByUid(uid) + } else { + DomainRulesManager.deleteAllRules() + } } } Utilities.showToastUiCentered( @@ -315,7 +323,7 @@ class CustomDomainFragment : } builder.setNegativeButton(getString(R.string.lbl_cancel)) { _, _ -> - // no-op + adapter.clearSelection() } builder.setCancelable(true) @@ -325,4 +333,8 @@ class CustomDomainFragment : private fun io(f: suspend () -> Unit) { lifecycleScope.launch(Dispatchers.IO) { f() } } + + private fun uiCtx(f: suspend () -> Unit) { + lifecycleScope.launch(Dispatchers.Main) { f() } + } } diff --git a/app/src/full/res/layout/list_item_custom_all_domain.xml b/app/src/full/res/layout/list_item_custom_all_domain.xml index 8f8d80886..ce34d1503 100644 --- a/app/src/full/res/layout/list_item_custom_all_domain.xml +++ b/app/src/full/res/layout/list_item_custom_all_domain.xml @@ -65,13 +65,21 @@ android:layout_margin="5dp" android:padding="5dp"> - + + - + + ) + @Transaction @Query("select * from CustomDomain order by modifiedTs desc") fun getAllDomains(): List diff --git a/app/src/main/java/com/celzero/bravedns/database/CustomDomainRepository.kt b/app/src/main/java/com/celzero/bravedns/database/CustomDomainRepository.kt index 5e2b3b1c4..b04bbaa08 100644 --- a/app/src/main/java/com/celzero/bravedns/database/CustomDomainRepository.kt +++ b/app/src/main/java/com/celzero/bravedns/database/CustomDomainRepository.kt @@ -84,4 +84,8 @@ class CustomDomainRepository(private val customDomainDAO: CustomDomainDAO) { fun getRulesCursor(): Cursor { return customDomainDAO.getRulesCursor() } + + suspend fun deleteRules(list: List) { + return customDomainDAO.deleteAll(list) + } } diff --git a/app/src/main/java/com/celzero/bravedns/service/DomainRulesManager.kt b/app/src/main/java/com/celzero/bravedns/service/DomainRulesManager.kt index 0f785b286..ef66c5382 100644 --- a/app/src/main/java/com/celzero/bravedns/service/DomainRulesManager.kt +++ b/app/src/main/java/com/celzero/bravedns/service/DomainRulesManager.kt @@ -312,6 +312,14 @@ object DomainRulesManager : KoinComponent { clearTrustedMap(uid) } + suspend fun deleteRules(list: List) { + list.forEach { cd -> + removeFromTrie(cd) + removeIfInTrustedMap(cd.uid, cd.domain) + } + db.deleteRules(list) + } + suspend fun deleteAllRules() { db.deleteAllRules() trie.clear() From b03a709b4472dbff7e6a0e12799b94ded5ebed89 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 22 Jul 2024 16:40:23 +0530 Subject: [PATCH 087/188] ui: #1551 retain invalid configs in wireguard screen show dialog to the user for invalid configs along with delete option in neutral btn. --- .../bravedns/service/WireguardManager.kt | 1 - .../ui/activity/WgConfigDetailActivity.kt | 28 ++++++++++++++++++- app/src/main/res/values/strings.xml | 2 ++ 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt b/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt index 4d0e0e680..b5a8f0c37 100644 --- a/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt +++ b/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt @@ -108,7 +108,6 @@ object WireguardManager : KoinComponent { EncryptedFileManager.readWireguardConfig(applicationContext, path) if (config == null) { Logger.e(LOG_TAG_PROXY, "error loading wg config: $path, deleting...") - db.deleteConfig(it.id) return@forEach } if (configs.none { i -> i.getId() == it.id }) { diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt index d2eb1d185..c1000cfbd 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt @@ -108,6 +108,18 @@ class WgConfigDetailActivity : AppCompatActivity(R.layout.activity_wg_detail) { } private fun init() { + if (!VpnController.hasTunnel()) { + Logger.i(LOG_TAG_PROXY, "VPN not active, config may not be available") + Utilities.showToastUiCentered( + this, + ERR_CODE_VPN_NOT_ACTIVE + + getString(R.string.settings_socks5_vpn_disabled_error), + Toast.LENGTH_LONG + ) + finish() + return + } + b.globalLockdownTitleTv.text = getString( R.string.two_argument_space, @@ -142,7 +154,7 @@ class WgConfigDetailActivity : AppCompatActivity(R.layout.activity_wg_detail) { val mapping = WireguardManager.getConfigFilesById(configId) if (config == null) { - finish() + showInvalidConfigDialog() return } @@ -173,6 +185,20 @@ class WgConfigDetailActivity : AppCompatActivity(R.layout.activity_wg_detail) { prefillConfig(config) } + private fun showInvalidConfigDialog() { + val builder = MaterialAlertDialogBuilder(this) + builder.setTitle(getString(R.string.lbl_wireguard)) + builder.setMessage(getString(R.string.config_invalid_desc)) + builder.setCancelable(false) + builder.setPositiveButton(getString(R.string.fapps_info_dialog_positive_btn)) { _, _ -> + finish() + } + builder.setNeutralButton(getString(R.string.lbl_delete)) { _, _ -> + WireguardManager.deleteConfig(configId) + } + builder.create().show() + } + private fun shouldObserveAppsCount(): Boolean { return !wgType.isOneWg() && !b.catchAllCheck.isChecked } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dbffb6a89..7481f95c9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1532,6 +1532,8 @@ Include remaining apps Include all remaining apps not routed by any other Proxy + Invalid WireGuard configuration + Does not block any DNS requests. Uses Cloudflare\'s 1.1.1.1 DNS endpoint. Blocks malware and adult content. Uses Cloudflare\'s 1.1.1.3 DNS endpoint. From bc651c21f530065ad574c5fd65c04bbf40872d92 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Tue, 23 Jul 2024 15:41:31 +0530 Subject: [PATCH 088/188] Fix: #1111, clear focus on searchview once soft keyboard is hidden --- .../bravedns/ui/activity/AppListActivity.kt | 49 +++++++++++++++++-- app/src/full/res/layout/activity_app_list.xml | 3 ++ 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/AppListActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/AppListActivity.kt index dc82917d8..84f27fdbb 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/AppListActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/AppListActivity.kt @@ -19,9 +19,12 @@ import android.content.Context import android.content.res.Configuration import android.graphics.PorterDuff import android.graphics.PorterDuffColorFilter +import android.graphics.Rect import android.os.Bundle +import android.util.TypedValue import android.view.LayoutInflater import android.view.View +import android.view.ViewTreeObserver import android.view.animation.Animation import android.view.animation.RotateAnimation import android.widget.CompoundButton @@ -201,6 +204,7 @@ class AppListActivity : override fun onResume() { super.onResume() setFirewallFilter(filters.value?.firewallFilter) + b.ffaAppList.requestFocus() } private fun initObserver() { @@ -239,11 +243,14 @@ class AppListActivity : override fun onPause() { filters.postValue(Filters()) + b.ffaSearch.clearFocus() + b.ffaAppList.requestFocus() super.onPause() } override fun onQueryTextSubmit(query: String): Boolean { addQueryToFilters(query) + b.ffaSearch.clearFocus() return true } @@ -720,6 +727,44 @@ class AppListActivity : b.ffaSearch.setOnQueryTextListener(this) addAnimation() remakeFirewallChipsUi() + handleKeyboardEvent() + } + + private fun handleKeyboardEvent() { + // ref: stackoverflow.com/a/36259261 + val rootView = findViewById(android.R.id.content) + + rootView.viewTreeObserver.addOnGlobalLayoutListener(object : + ViewTreeObserver.OnGlobalLayoutListener { + private var alreadyOpen = false + private val defaultKeyboardHeightDP = 100 + private val EstimatedKeyboardDP = defaultKeyboardHeightDP + 48 + private val rect = Rect() + + override fun onGlobalLayout() { + val estimatedKeyboardHeight = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + EstimatedKeyboardDP.toFloat(), + rootView.resources.displayMetrics + ).toInt() + rootView.getWindowVisibleDisplayFrame(rect) + val heightDiff = rootView.rootView.height - (rect.bottom - rect.top) + val isShown = heightDiff >= estimatedKeyboardHeight + + if (isShown == alreadyOpen) { + return // nothing to do + } + + alreadyOpen = isShown + + if (!isShown) { + if (b.ffaSearch.hasFocus()) { + // clear focus from search view when keyboard is closed + b.ffaSearch.clearFocus() + } + } + } + }) } private fun initListAdapter() { @@ -762,8 +807,4 @@ class AppListActivity : private fun io(f: suspend () -> Unit) { lifecycleScope.launch(Dispatchers.IO) { f() } } - - private fun ui(f: () -> Unit) { - lifecycleScope.launch(Dispatchers.Main) { f() } - } } diff --git a/app/src/full/res/layout/activity_app_list.xml b/app/src/full/res/layout/activity_app_list.xml index 2bfd3f2e5..02cdba569 100644 --- a/app/src/full/res/layout/activity_app_list.xml +++ b/app/src/full/res/layout/activity_app_list.xml @@ -4,6 +4,8 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="?attr/background" + android:descendantFocusability="beforeDescendants" + android:focusableInTouchMode="true" app:layout_behavior="@string/appbar_scrolling_view_behavior"> From 1ac241c6803c75dc60f3f2f704d41650d10c2ae5 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Tue, 23 Jul 2024 17:03:13 +0530 Subject: [PATCH 089/188] ui: alpha for apps with no-internet permission Consider non-apps as apps with internet as the entry would have made when there is a connection request from that uid. --- .../java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt | 2 +- app/src/main/java/com/celzero/bravedns/database/AppInfo.kt | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt index 3765cf2af..6b73934cc 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt @@ -116,7 +116,7 @@ class FirewallAppListAdapter( if (appInfo.hasInternetPermission(packageManager)) { b.firewallAppLabelTv.alpha = 1f } else { - b.firewallAppLabelTv.alpha = 0.6f + b.firewallAppLabelTv.alpha = 0.4f } b.firewallAppToggleOther.text = getFirewallText(appStatus, connStatus) displayIcon( diff --git a/app/src/main/java/com/celzero/bravedns/database/AppInfo.kt b/app/src/main/java/com/celzero/bravedns/database/AppInfo.kt index 0d599bce9..acc937797 100644 --- a/app/src/main/java/com/celzero/bravedns/database/AppInfo.kt +++ b/app/src/main/java/com/celzero/bravedns/database/AppInfo.kt @@ -104,6 +104,8 @@ class AppInfo { } fun hasInternetPermission(packageManager: PackageManager): Boolean { + if (packageName.startsWith("no_package_")) return true + // INTERNET permission if defined, can not be denied so this is safe to use return packageManager.checkPermission(Manifest.permission.INTERNET, packageName) == PackageManager.PERMISSION_GRANTED } From 07fbd02d82bef8209b7415d593ff7350cb128ab4 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Tue, 23 Jul 2024 17:19:56 +0530 Subject: [PATCH 090/188] fix #1554: appropriate label for private dns firewall --- .../celzero/bravedns/service/BraveVPNService.kt | 2 +- .../celzero/bravedns/service/FirewallRuleset.kt | 14 ++++++++++++-- app/src/main/res/values/strings.xml | 2 ++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt b/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt index fdf0ea1dc..5445628e6 100644 --- a/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt +++ b/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt @@ -3409,7 +3409,7 @@ class BraveVPNService : if (trapVpnPrivateDns) { logd("flow: dns-over-tls, returning Ipn.Block, $uid") cm.isBlocked = true - cm.blockedByRule = FirewallRuleset.RULE1C.id + cm.blockedByRule = FirewallRuleset.RULE14.id return@runBlocking persistAndConstructFlowResponse(cm, Backend.Block, connId, uid) } diff --git a/app/src/main/java/com/celzero/bravedns/service/FirewallRuleset.kt b/app/src/main/java/com/celzero/bravedns/service/FirewallRuleset.kt index 368d8c699..2537c90ea 100644 --- a/app/src/main/java/com/celzero/bravedns/service/FirewallRuleset.kt +++ b/app/src/main/java/com/celzero/bravedns/service/FirewallRuleset.kt @@ -200,6 +200,12 @@ enum class FirewallRuleset(val id: String, val title: Int, val desc: Int, val ac R.string.firewall_rule_block_app_desc, R.integer.stall ), + RULE14( + "Private DNS", + R.string.firewall_rule_private_dns, + R.string.firewall_rule_private_dns_desc, + R.integer.stall + ), ; companion object { @@ -234,6 +240,8 @@ enum class FirewallRuleset(val id: String, val title: Int, val desc: Int, val ac RULE10.id -> RULE10 RULE11.id -> RULE11 RULE12.id -> RULE12 + RULE13.id -> RULE13 + RULE14.id -> RULE14 else -> null } } @@ -270,16 +278,18 @@ enum class FirewallRuleset(val id: String, val title: Int, val desc: Int, val ac RULE10.id -> R.drawable.ic_http RULE11.id -> R.drawable.ic_global_lockdown RULE12.id -> R.drawable.ic_proxy_white + RULE13.id -> R.drawable.ic_proxy_white + RULE14.id -> R.drawable.bs_dns_home_screen else -> R.drawable.bs_dns_home_screen } } fun getAllowedRules(): List { - return values().toList().filter { it.act == R.integer.allow } + return entries.filter { it.act == R.integer.allow } } fun getBlockedRules(): List { - return values().toList().filter { it.act != R.integer.allow } + return entries.filter { it.act != R.integer.allow } } fun ground(rule: FirewallRuleset): Boolean { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7481f95c9..3b54a61c0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1146,6 +1146,7 @@ Domain (Univ) Trusted Domain (Univ) Proxied + Private DNS No firewall rules matched this connection. app.

To change go to All Apps tab.]]> @@ -1175,6 +1176,7 @@ domain is set as blocked in Universal domain rules.

To change this behaviour go to Configure DNS tab.]]>
domain is set as trusted in Universal domain rules.

To change this behaviour go to Configure DNS tab.]]>

To change go to Proxy from Configure screen.]]>
+ Private DNS is enabled.

To change this behaviour disable Private DNS from Android Settings screen.]]>
Attention From 8c1021e5c73036e61dcc8b0491b87db05e91e0f9 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 27 Jul 2024 17:19:26 +0530 Subject: [PATCH 091/188] ui: rmv firewall indicator, add data usage for apps --- .../adapter/FirewallAppListAdapter.kt | 73 ++++++------------- .../res/layout/list_item_firewall_app.xml | 20 +++-- 2 files changed, 34 insertions(+), 59 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt index 6b73934cc..3a4d536af 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt @@ -40,6 +40,8 @@ import com.celzero.bravedns.database.AppInfo import com.celzero.bravedns.databinding.ListItemFirewallAppBinding import com.celzero.bravedns.service.FirewallManager import com.celzero.bravedns.service.FirewallManager.updateFirewallStatus +import com.celzero.bravedns.service.ProxyManager +import com.celzero.bravedns.service.ProxyManager.ID_NONE import com.celzero.bravedns.ui.activity.AppInfoActivity import com.celzero.bravedns.ui.activity.AppInfoActivity.Companion.UID_INTENT_NAME import com.celzero.bravedns.util.UIUtils @@ -132,11 +134,30 @@ class FirewallAppListAdapter( b.firewallAppToggleWifi.visibility = View.VISIBLE b.firewallAppToggleMobileData.visibility = View.VISIBLE displayConnectionStatus(appStatus, connStatus) - showAppHint(b.firewallAppStatusIndicator, appInfo) + displayDataUsage(appInfo) + maybeDisplayProxyStatus(appInfo) } } } + private fun displayDataUsage(appInfo: AppInfo) { + val u = Utilities.humanReadableByteCount(appInfo.uploadBytes, true) + val uploadBytes = context.getString(R.string.symbol_upload, u) + val d = Utilities.humanReadableByteCount(appInfo.downloadBytes, true) + val downloadBytes = context.getString(R.string.symbol_download, d) + b.firewallAppDataUsage.text = + context.getString(R.string.two_argument, uploadBytes, downloadBytes) + } + + private fun maybeDisplayProxyStatus(appInfo: AppInfo) { + // show key icon in drawable right of b.firewallAppDataUsage + val proxy = ProxyManager.getProxyIdForApp(appInfo.uid) + if (proxy.isEmpty() || proxy == ID_NONE) { + return + } + b.firewallAppLabelTv.append(context.getString(R.string.symbol_key)) + } + private fun getFirewallText( aStat: FirewallManager.FirewallStatus, cStat: FirewallManager.ConnectionStatus @@ -244,56 +265,6 @@ class FirewallAppListAdapter( ContextCompat.getDrawable(context, R.drawable.ic_firewall_wifi_on_grey)) } - private fun showAppHint(mIconIndicator: TextView, appInfo: AppInfo) { - io { - val connStatus = FirewallManager.connectionStatus(appInfo.uid) - val appStatus = FirewallManager.appStatus(appInfo.uid) - uiCtx { - when (appStatus) { - FirewallManager.FirewallStatus.NONE -> { - when (connStatus) { - FirewallManager.ConnectionStatus.ALLOW -> { - mIconIndicator.setBackgroundColor( - context.getColor(R.color.colorGreen_900)) - } - FirewallManager.ConnectionStatus.METERED -> { - mIconIndicator.setBackgroundColor( - context.getColor(R.color.colorAmber_900)) - } - FirewallManager.ConnectionStatus.UNMETERED -> { - mIconIndicator.setBackgroundColor( - context.getColor(R.color.colorAmber_900)) - } - FirewallManager.ConnectionStatus.BOTH -> { - mIconIndicator.setBackgroundColor( - context.getColor(R.color.colorAmber_900)) - } - } - } - FirewallManager.FirewallStatus.EXCLUDE -> { - mIconIndicator.setBackgroundColor( - context.getColor(R.color.primaryLightColorText)) - } - FirewallManager.FirewallStatus.BYPASS_UNIVERSAL -> { - mIconIndicator.setBackgroundColor( - context.getColor(R.color.primaryLightColorText)) - } - FirewallManager.FirewallStatus.BYPASS_DNS_FIREWALL -> { - mIconIndicator.setBackgroundColor( - context.getColor(R.color.primaryLightColorText)) - } - FirewallManager.FirewallStatus.ISOLATE -> { - mIconIndicator.setBackgroundColor( - context.getColor(R.color.colorAmber_900)) - } - FirewallManager.FirewallStatus.UNTRACKED -> { - /* no-op */ - } - } - } - } - } - private fun displayIcon(drawable: Drawable?, mIconImageView: ImageView) { ui { Glide.with(context) diff --git a/app/src/full/res/layout/list_item_firewall_app.xml b/app/src/full/res/layout/list_item_firewall_app.xml index 7626ecc33..c448ffd9c 100644 --- a/app/src/full/res/layout/list_item_firewall_app.xml +++ b/app/src/full/res/layout/list_item_firewall_app.xml @@ -13,17 +13,11 @@ android:layout_height="wrap_content" android:orientation="horizontal"> - - - + + + From ed0f44efb3d739f43d4b50ccc47c5355830fe8cb Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 27 Jul 2024 17:20:11 +0530 Subject: [PATCH 092/188] ui: display proxy details in app info screen --- .../bravedns/ui/activity/AppInfoActivity.kt | 18 ++++++-- .../full/res/layout/activity_app_details.xml | 46 ++++++++++++------- 2 files changed, 45 insertions(+), 19 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/AppInfoActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/AppInfoActivity.kt index 80c04c5b0..810d56967 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/AppInfoActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/AppInfoActivity.kt @@ -38,11 +38,12 @@ import com.celzero.bravedns.R import com.celzero.bravedns.adapter.AppWiseDomainsAdapter import com.celzero.bravedns.adapter.AppWiseIpsAdapter import com.celzero.bravedns.database.AppInfo -import com.celzero.bravedns.database.ConnectionTrackerRepository import com.celzero.bravedns.databinding.ActivityAppDetailsBinding import com.celzero.bravedns.service.FirewallManager import com.celzero.bravedns.service.FirewallManager.updateFirewallStatus import com.celzero.bravedns.service.PersistentState +import com.celzero.bravedns.service.ProxyManager +import com.celzero.bravedns.service.ProxyManager.ID_NONE import com.celzero.bravedns.service.VpnController import com.celzero.bravedns.util.Constants import com.celzero.bravedns.util.Constants.Companion.INVALID_UID @@ -125,7 +126,8 @@ class AppInfoActivity : AppCompatActivity(R.layout.activity_app_details) { b.aadAppDetailName.text = appName(packages.count()) b.aadPkgName.text = appInfo.packageName b.excludeProxySwitch.isChecked = appInfo.isProxyExcluded - updateDataUsage() + displayDataUsage() + displayProxyStatus() displayIcon( Utilities.getIcon(this, appInfo.packageName, appInfo.appName), b.aadAppDetailIcon @@ -150,6 +152,16 @@ class AppInfoActivity : AppCompatActivity(R.layout.activity_app_details) { } } + private fun displayProxyStatus() { + val proxy = ProxyManager.getProxyIdForApp(appInfo.uid) + if (proxy.isEmpty() || proxy == ID_NONE) { + b.aadProxyDetails.visibility = View.GONE + return + } + b.aadProxyDetails.visibility = View.VISIBLE + b.aadProxyDetails.text = getString(R.string.wireguard_apps_proxy_map_desc, proxy) + } + private fun hideFirewallStatusUi() { b.aadAppSettingsCard.visibility = View.GONE } @@ -185,7 +197,7 @@ class AppInfoActivity : AppCompatActivity(R.layout.activity_app_details) { startActivity(intent) } - private fun updateDataUsage() { + private fun displayDataUsage() { val u = Utilities.humanReadableByteCount(appInfo.uploadBytes, true) val uploadBytes = getString(R.string.symbol_upload, u) val d = Utilities.humanReadableByteCount(appInfo.downloadBytes, true) diff --git a/app/src/full/res/layout/activity_app_details.xml b/app/src/full/res/layout/activity_app_details.xml index 897472691..e85a6f110 100644 --- a/app/src/full/res/layout/activity_app_details.xml +++ b/app/src/full/res/layout/activity_app_details.xml @@ -38,7 +38,7 @@ android:layout_toEndOf="@id/aad_app_detail_icon" android:orientation="vertical"> - - - - @@ -143,7 +149,7 @@ android:gravity="center" android:weightSum="1"> - - - - - - - - + + + - - - - Date: Sat, 27 Jul 2024 17:22:07 +0530 Subject: [PATCH 093/188] minor ui improvements in wireguard screens --- .../celzero/bravedns/adapter/WgConfigAdapter.kt | 14 +++++++------- .../full/res/layout/activity_tunnel_settings.xml | 7 +++++-- app/src/main/res/layout/list_item_wg_peers.xml | 2 +- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt index 09bae1316..9d473c7c6 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt @@ -128,21 +128,21 @@ class WgConfigAdapter(private val context: Context) : if (config.isLockdown) { b.protocolInfoChipGroup.visibility = View.GONE b.interfaceActiveLayout.visibility = View.GONE - b.interfaceConfigStatus.text = + b.interfaceStatus.text = context.getString(R.string.lbl_disabled).replaceFirstChar(Char::titlecase) val id = ProxyManager.ID_WG_BASE + config.id val appsCount = ProxyManager.getAppCountForProxy(id) updateUi(config, appsCount) } else { - b.interfaceStatus.visibility = View.GONE + b.interfaceConfigStatus.visibility = View.GONE b.interfaceAppsCount.visibility = View.GONE b.interfaceActiveLayout.visibility = View.GONE b.interfaceDetailCard.strokeColor = UIUtils.fetchColor(context, R.attr.background) b.interfaceDetailCard.strokeWidth = 0 b.interfaceSwitch.isChecked = false b.protocolInfoChipGroup.visibility = View.GONE - b.interfaceConfigStatus.visibility = View.VISIBLE - b.interfaceConfigStatus.text = + b.interfaceStatus.visibility = View.VISIBLE + b.interfaceStatus.text = context.getString(R.string.lbl_disabled).replaceFirstChar(Char::titlecase) } } @@ -353,10 +353,10 @@ class WgConfigAdapter(private val context: Context) : b.interfaceDetailCard.strokeColor = UIUtils.fetchColor(context, R.attr.background) b.interfaceDetailCard.strokeWidth = 0 b.interfaceSwitch.isChecked = false - b.interfaceStatus.visibility = View.GONE + b.interfaceConfigStatus.visibility = View.GONE b.interfaceAppsCount.visibility = View.GONE - b.interfaceConfigStatus.visibility = View.VISIBLE - b.interfaceConfigStatus.text = + b.interfaceStatus.visibility = View.VISIBLE + b.interfaceStatus.text = context.getString(R.string.lbl_disabled).replaceFirstChar(Char::titlecase) } } diff --git a/app/src/full/res/layout/activity_tunnel_settings.xml b/app/src/full/res/layout/activity_tunnel_settings.xml index f3721f055..93fd9f534 100644 --- a/app/src/full/res/layout/activity_tunnel_settings.xml +++ b/app/src/full/res/layout/activity_tunnel_settings.xml @@ -579,7 +579,7 @@ - diff --git a/app/src/main/res/layout/list_item_wg_peers.xml b/app/src/main/res/layout/list_item_wg_peers.xml index 3d372ec23..194d97e8b 100644 --- a/app/src/main/res/layout/list_item_wg_peers.xml +++ b/app/src/main/res/layout/list_item_wg_peers.xml @@ -26,7 +26,7 @@ android:layout_height="wrap_content" android:text="@string/lbl_peer" android:textAppearance="?attr/textAppearanceHeadline6" - android:textColor="?attr/secondaryTextColor" + android:textColor="?attr/primaryTextColor" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> From f85c91d6fb8f0f4f73fa76eac3b6febc3603cb42 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 27 Jul 2024 17:24:03 +0530 Subject: [PATCH 094/188] Logger: separate worker thread for console logs --- .../celzero/bravedns/service/NetLogTracker.kt | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/celzero/bravedns/service/NetLogTracker.kt b/app/src/main/java/com/celzero/bravedns/service/NetLogTracker.kt index 8a2c6dc90..86eecabc1 100644 --- a/app/src/main/java/com/celzero/bravedns/service/NetLogTracker.kt +++ b/app/src/main/java/com/celzero/bravedns/service/NetLogTracker.kt @@ -37,6 +37,7 @@ import java.util.Calendar import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.ExecutorCoroutineDispatcher import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.awaitCancellation import kotlinx.coroutines.delay @@ -74,13 +75,15 @@ internal constructor( // looper is never closed / cancelled and is always active private val looper = Daemons.make("netlog") + private val consoleLogLooper = Daemons.make("consoleLog") + companion object { private const val UPDATE_DELAY = 2500L } suspend fun restart(s: CoroutineScope) { this.scope = s - serializer("restart") { + serializer("restart", looper) { // create new batchers on every new scope as their lifecycle is tied to the scope val b1 = NetLogBatcher("dns", looper, dnsdb::insertBatch) @@ -99,7 +102,7 @@ internal constructor( ipdb::updateRethinkBatch ) val b4 = - NetLogBatcher("console", looper, consoleLogDb::insertBatch) + NetLogBatcher("console", consoleLogLooper, consoleLogDb::insertBatch) b1.begin(s) b2.begin(s) @@ -125,20 +128,23 @@ internal constructor( dnsBatcher?.close() ipBatcher?.close() rrBatcher?.close() - consoleLogBatcher?.close() dnsBatcher = null ipBatcher = null rrBatcher = null - consoleLogBatcher = null Logger.d(LOG_BATCH_LOGGER, "tracker: close scope") } + withContext(consoleLogLooper + NonCancellable) { + consoleLogBatcher?.close() + consoleLogBatcher = null + Logger.d(LOG_BATCH_LOGGER, "tracker: close consoleLogLooper") + } } } fun writeIpLog(info: ConnTrackerMetaData) { if (!persistentState.logsEnabled) return - serializer("writeIpLog") { + serializer("writeIpLog", looper) { val connTracker = ipdb.makeConnectionTracker(info) ipBatcher?.add(connTracker) } @@ -147,7 +153,7 @@ internal constructor( fun writeRethinkLog(info: ConnTrackerMetaData) { if (!persistentState.logsEnabled) return - serializer("writeRethinkLog") { + serializer("writeRethinkLog", looper) { val rlog = ipdb.makeRethinkLogs(info) ?: return@serializer rrBatcher?.add(rlog) } @@ -158,7 +164,7 @@ internal constructor( val d = Logger.LoggerType.fromId(persistentState.goLoggerLevel.toInt()) val debug = d.isLessThan(Logger.LoggerType.INFO) // debug, verbose, very verbose - serializer("updateIpSmm") { + serializer("updateIpSmm", looper) { val s = if (debug && summary.targetIp?.isNotEmpty() == true) { ipdb.makeSummaryWithTarget(summary) @@ -176,7 +182,7 @@ internal constructor( fun updateRethinkSummary(summary: ConnectionSummary) { if (!persistentState.logsEnabled) return - serializer("updateRethinkSmm") { + serializer("updateRethinkSmm", looper) { val s = if (DEBUG && summary.targetIp?.isNotEmpty() == true) { ipdb.makeSummaryWithTarget(summary) @@ -198,7 +204,7 @@ internal constructor( transaction.responseCalendar = Calendar.getInstance() // TODO: move this to generic Dispatcher.IO; serializer is not required - serializer("refreshDnsLatency") { dnsLatencyTracker.refreshLatencyIfNeeded(transaction) } + serializer("refreshDnsLatency", looper) { dnsLatencyTracker.refreshLatencyIfNeeded(transaction) } // TODO: This method should be part of BraveVPNService dnsdb.updateVpnConnectionState(transaction) @@ -206,20 +212,16 @@ internal constructor( if (!persistentState.logsEnabled) return val dnsLog = dnsdb.makeDnsLogObj(transaction) - serializer("writeDnsLog") { dnsBatcher?.add(dnsLog) } + serializer("writeDnsLog", looper) { dnsBatcher?.add(dnsLog) } } fun writeConsoleLog(log: ConsoleLog) { - io("writeConsoleLog") { + serializer("writeConsoleLog", consoleLogLooper) { consoleLogBatcher?.add(log) } } - private fun serializer(s: String, f: suspend () -> Unit) = - scope?.launch(CoroutineName(s) + looper) { f() } - ?: Log.e(LOG_BATCH_LOGGER, "scope is null", Exception()) - - private fun io(s: String, f: suspend () -> Unit) = - scope?.launch(CoroutineName(s) + Dispatchers.IO) { f() } + private fun serializer(s: String, e: ExecutorCoroutineDispatcher, f: suspend () -> Unit) = + scope?.launch(CoroutineName(s) + e) { f() } ?: Log.e(LOG_BATCH_LOGGER, "scope is null", Exception()) } From 84fde544cee38127319ffb03bc6c8f628f1b897a Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 27 Jul 2024 17:27:47 +0530 Subject: [PATCH 095/188] tunnel: initiate wg ping on connect, notify loopback, rmv slowdown --- .../java/com/celzero/bravedns/net/go/GoVpnAdapter.kt | 12 +++++++----- .../com/celzero/bravedns/service/BraveVPNService.kt | 4 ++-- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/celzero/bravedns/net/go/GoVpnAdapter.kt b/app/src/main/java/com/celzero/bravedns/net/go/GoVpnAdapter.kt index 5a40801dd..a1c879dfd 100644 --- a/app/src/main/java/com/celzero/bravedns/net/go/GoVpnAdapter.kt +++ b/app/src/main/java/com/celzero/bravedns/net/go/GoVpnAdapter.kt @@ -118,7 +118,7 @@ class GoVpnAdapter : KoinComponent { setRDNS() addTransport() setDnsAlg() - maybeSlowdown() + notifyLoopback() Logger.v(LOG_TAG_VPN, "GoVpnAdapter initResolverProxiesPcap done") } @@ -757,6 +757,8 @@ class GoVpnAdapter : KoinComponent { val wgUserSpaceString = wgConfig?.toWgUserspaceString(isOneWg) getProxies()?.addProxy(id, wgUserSpaceString) if (isOneWg) setWireGuardDns(id) + // initiate a ping request to the wg proxy + initiateWgPing(id) Logger.i(LOG_TAG_VPN, "add wireguard proxy with $id; dns? $isOneWg") } catch (e: Exception) { Logger.e(LOG_TAG_VPN, "err adding wireguard proxy: ${e.message}", e) @@ -987,13 +989,13 @@ class GoVpnAdapter : KoinComponent { } } - suspend fun maybeSlowdown() { + suspend fun notifyLoopback() { val t = persistentState.routeRethinkInRethink || VpnController.isVpnLockdown() try { - Intra.slowdown(t) - Logger.i(LOG_TAG_VPN, "maybe slowdown? $t") + Intra.loopback(t) + Logger.i(LOG_TAG_VPN, "notify loopback? $t") } catch (e: Exception) { - Logger.e(LOG_TAG_VPN, "err slowdown? $t, ${e.message}", e) + Logger.e(LOG_TAG_VPN, "err notify loopback? $t, ${e.message}", e) } } diff --git a/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt b/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt index 5445628e6..a4eef8e26 100644 --- a/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt +++ b/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt @@ -1848,7 +1848,7 @@ class BraveVPNService : // restart vpn to allow/disallow rethink traffic in rethink io("routeRethinkInRethink") { restartVpnWithNewAppConfig(reason = "routeRethinkInRethink") - vpnAdapter?.maybeSlowdown() + vpnAdapter?.notifyLoopback() } } @@ -2445,7 +2445,7 @@ class BraveVPNService : Logger.i(LOG_TAG_VPN, "vpn lockdown mode change, restarting") io("lockdownSync") { restartVpnWithNewAppConfig(reason = "lockdownSync") - vpnAdapter?.maybeSlowdown() + vpnAdapter?.notifyLoopback() } } From aa873bebdaa42e79822ae9113531d2fa6c7f5537 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 29 Jul 2024 18:01:39 +0530 Subject: [PATCH 096/188] ui: display protos, traffic stats and bandwidth, plus icon --- .../ui/fragment/HomeScreenFragment.kt | 81 ++++++++++++++----- .../full/res/layout/fragment_home_screen.xml | 18 +++++ app/src/main/res/drawable/ic_plus_accent.xml | 24 ++++++ 3 files changed, 102 insertions(+), 21 deletions(-) create mode 100644 app/src/main/res/drawable/ic_plus_accent.xml diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt index 40f5c8f6d..f1c94843a 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt @@ -39,7 +39,6 @@ import android.provider.Settings import android.text.format.DateUtils import android.util.TypedValue import android.view.View -import android.view.animation.AnimationUtils import android.widget.Toast import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResultLauncher @@ -199,6 +198,11 @@ class HomeScreenFragment : Fragment(R.layout.fragment_home_screen) { startActivity(intent) } + b.fhsSponsorBottom.setOnClickListener { + val intent = Intent(Intent.ACTION_VIEW, RETHINKDNS_SPONSOR_LINK.toUri()) + startActivity(intent) + } + b.fhsTitleRethink.setOnClickListener { val intent = Intent(Intent.ACTION_VIEW, RETHINKDNS_SPONSOR_LINK.toUri()) startActivity(intent) @@ -928,6 +932,7 @@ class HomeScreenFragment : Fragment(R.layout.fragment_home_screen) { syncDnsStatus() handleLockdownModeIfNeeded() startTrafficStats() + b.fhsSponsorBottom.bringToFront() } private lateinit var trafficStatsTicker: Job @@ -937,43 +942,50 @@ class HomeScreenFragment : Fragment(R.layout.fragment_home_screen) { ui("trafficStatsTicker") { var counter = 0 while (true) { - if (counter % 2 == 0) { - fetchTrafficStats() + // make it as 3 options and add the protos + if (!isAdded) return@ui + + if (counter % 3 == 0) { + displayTrafficStatsRate() + } else if (counter % 3 == 1) { + displayTrafficStatsBW() } else { - fetchNetStats() + displayProtos() } + // show protos kotlinx.coroutines.delay(2500L) counter++ } } } - private fun fetchNetStats() { - val stat = VpnController.getNetStat() - val nic = stat?.nic() + private fun displayProtos() { + b.fhsInternetSpeed.visibility = View.VISIBLE + b.fhsInternetSpeedUnit.visibility = View.VISIBLE + b.fhsInternetSpeed.text = VpnController.protocols() + b.fhsInternetSpeedUnit.text = getString(R.string.lbl_protos) + } - // show the stats in MB - val txBytes = String.format("%.2f", (nic?.txBytes ?: 0) / 1000000.0) - val rxBytes = String.format("%.2f", (nic?.rxBytes ?: 0) / 1000000.0) + private fun displayTrafficStatsBW() { + val txRx = convertToCommonUnit(txRx.tx, txRx.rx) b.fhsInternetSpeed.visibility = View.VISIBLE b.fhsInternetSpeedUnit.visibility = View.VISIBLE - // for netstack: rx: up, tx: down b.fhsInternetSpeed.text = getString( R.string.two_argument_space, getString( R.string.two_argument_space, - rxBytes, + txRx.first, getString(R.string.symbol_black_up) ), getString( R.string.two_argument_space, - txBytes, + txRx.second, getString(R.string.symbol_black_down) ) ) - b.fhsInternetSpeedUnit.text = getString(R.string.symbol_mb) + b.fhsInternetSpeedUnit.text = getCommonUnit(this.txRx.tx, this.txRx.rx) } private fun stopTrafficStats() { @@ -992,7 +1004,7 @@ class HomeScreenFragment : Fragment(R.layout.fragment_home_screen) { private var txRx = TxRx() - private fun fetchTrafficStats() { + private fun displayTrafficStatsRate() { val curr = TxRx() if (txRx.time <= 0L) { txRx = curr @@ -1007,12 +1019,10 @@ class HomeScreenFragment : Fragment(R.layout.fragment_home_screen) { b.fhsInternetSpeedUnit.visibility = View.GONE return } - val tx = curr.tx - txRx.tx val rx = curr.rx - txRx.rx txRx = curr - val txBytes = String.format("%.2f", ((tx / dur) / 1000.0)) - val rxBytes = String.format("%.2f", ((rx / dur) / 1000.0)) + val txRx = convertToCommonUnit(tx/dur, rx/dur) b.fhsInternetSpeed.visibility = View.VISIBLE b.fhsInternetSpeedUnit.visibility = View.VISIBLE b.fhsInternetSpeed.text = @@ -1020,18 +1030,47 @@ class HomeScreenFragment : Fragment(R.layout.fragment_home_screen) { R.string.two_argument_space, getString( R.string.two_argument_space, - txBytes, + txRx.first, getString(R.string.symbol_black_up) ), getString( R.string.two_argument_space, - rxBytes, + txRx.second, getString(R.string.symbol_black_down) ) ) - b.fhsInternetSpeedUnit.text = getString(R.string.symbol_kbs) + b.fhsInternetSpeedUnit.text = getString(R.string.symbol_ps, getCommonUnit(tx/dur, rx/dur)) + } + + // TODO: Move this to a common utility class + private fun getCommonUnit(bytes1: Long, bytes2: Long): String { + val maxBytes = maxOf(bytes1, bytes2) + return when { + maxBytes >= 1024L * 1024L * 1024L * 1024L -> "TB" + maxBytes >= 1024L * 1024L * 1024L -> "GB" + maxBytes >= 1024L * 1024L -> "MB" + maxBytes >= 1024L -> "KB" + else -> "B" + } } + private fun convertToCommonUnit(bytes1: Long, bytes2: Long): Pair { + val unit = getCommonUnit(bytes1, bytes2) + val v = when (unit) { + "TB" -> Pair(bytesToTB(bytes1), bytesToTB(bytes2)) + "GB" -> Pair(bytesToGB(bytes1), bytesToGB(bytes2)) + "MB" -> Pair(bytesToMB(bytes1), bytesToMB(bytes2)) + "KB" -> Pair(bytesToKB(bytes1), bytesToKB(bytes2)) + else -> Pair(bytes1.toDouble(), bytes2.toDouble()) + } + return Pair(String.format(Locale.ROOT, "%.2f", v.first), String.format(Locale.ROOT, "%.2f", v.second)) + } + + private fun bytesToKB(bytes: Long): Double = bytes / 1024.0 + private fun bytesToMB(bytes: Long): Double = bytes / (1024.0 * 1024.0) + private fun bytesToGB(bytes: Long): Double = bytes / (1024.0 * 1024.0 * 1024.0) + private fun bytesToTB(bytes: Long): Double = bytes / (1024.0 * 1024.0 * 1024.0 * 1024.0) + /** * Issue fix - https://github.com/celzero/rethink-app/issues/57 When the application * crashes/updates it goes into red waiting state. This causes confusion to the users also diff --git a/app/src/full/res/layout/fragment_home_screen.xml b/app/src/full/res/layout/fragment_home_screen.xml index 1fb0380a1..ddc3743cd 100644 --- a/app/src/full/res/layout/fragment_home_screen.xml +++ b/app/src/full/res/layout/fragment_home_screen.xml @@ -45,7 +45,13 @@ + + + + + + + + + + From 7fece25cf81392559c7e6c9bf59d6074df8204e7 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 29 Jul 2024 18:02:50 +0530 Subject: [PATCH 097/188] ui: wireguard name text color change --- app/src/main/res/layout/activity_wg_detail.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/activity_wg_detail.xml b/app/src/main/res/layout/activity_wg_detail.xml index 0333dd425..b707a57ff 100644 --- a/app/src/main/res/layout/activity_wg_detail.xml +++ b/app/src/main/res/layout/activity_wg_detail.xml @@ -262,7 +262,7 @@ android:layout_toStartOf="@id/interface_edit" android:text="" android:textAppearance="?attr/textAppearanceHeadline6" - android:textColor="?attr/secondaryTextColor" /> + android:textColor="?attr/primaryTextColor" /> Date: Mon, 29 Jul 2024 18:14:20 +0530 Subject: [PATCH 098/188] new-feature: camera and microphone access indicator added camera and microphone access indicators using accessibility services. Currently, the UI is not shown to the user. Further fine-tuning is needed. --- .../ui/activity/MiscSettingsActivity.kt | 123 +++++++- .../util/BackgroundAccessibilityService.kt | 276 +++++++++++++++++- .../res/layout/activity_misc_settings.xml | 58 ++++ .../bravedns/service/PersistentState.kt | 3 + .../java/com/celzero/bravedns/util/Logger.kt | 1 + app/src/main/res/drawable/ic_camera.png | Bin 0 -> 4759 bytes app/src/main/res/drawable/ic_microphone.png | Bin 0 -> 4585 bytes .../res/layout/mic_cam_access_indicator.xml | 41 +++ 8 files changed, 489 insertions(+), 13 deletions(-) create mode 100644 app/src/main/res/drawable/ic_camera.png create mode 100644 app/src/main/res/drawable/ic_microphone.png create mode 100644 app/src/main/res/layout/mic_cam_access_indicator.xml diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/MiscSettingsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/MiscSettingsActivity.kt index 6386f5610..923ae287f 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/MiscSettingsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/MiscSettingsActivity.kt @@ -16,6 +16,7 @@ package com.celzero.bravedns.ui.activity import Logger +import Logger.LOG_TAG_APP_OPS import Logger.LOG_TAG_UI import Logger.LOG_TAG_VPN import Logger.updateConfigLevel @@ -54,6 +55,7 @@ import com.celzero.bravedns.databinding.ActivityMiscSettingsBinding import com.celzero.bravedns.net.go.GoVpnAdapter import com.celzero.bravedns.service.PersistentState import com.celzero.bravedns.ui.bottomsheet.BackupRestoreBottomSheet +import com.celzero.bravedns.util.BackgroundAccessibilityService import com.celzero.bravedns.util.Constants import com.celzero.bravedns.util.NotificationActionType import com.celzero.bravedns.util.PcapMode @@ -64,7 +66,6 @@ import com.celzero.bravedns.util.Utilities.delay import com.celzero.bravedns.util.Utilities.isAtleastR import com.celzero.bravedns.util.Utilities.isAtleastT import com.celzero.bravedns.util.Utilities.isFdroidFlavour -import com.celzero.bravedns.util.Utilities.isPlayStoreFlavour import com.celzero.bravedns.util.Utilities.showToastUiCentered import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.koin.android.ext.android.inject @@ -77,6 +78,7 @@ import java.util.Date import java.util.Locale import java.util.concurrent.TimeUnit + class MiscSettingsActivity : AppCompatActivity(R.layout.activity_misc_settings) { private val b by viewBinding(ActivityMiscSettingsBinding::bind) @@ -110,6 +112,8 @@ class MiscSettingsActivity : AppCompatActivity(R.layout.activity_misc_settings) b.settingsActivityAutoStartSwitch.isChecked = persistentState.prefAutoStartBootUp // check for app updates b.settingsActivityCheckUpdateSwitch.isChecked = persistentState.checkForAppUpdate + // camera and microphone access + b.settingsMicCamAccessSwitch.isChecked = persistentState.micCamAccess // for app locale (default system/user selected locale) if (isAtleastT()) { @@ -312,11 +316,123 @@ class MiscSettingsActivity : AppCompatActivity(R.layout.activity_misc_settings) b.settingsConsoleLogRl.setOnClickListener { openConsoleLogActivity() } + + b.settingsMicCamAccessRl.setOnClickListener { + b.settingsMicCamAccessSwitch.isChecked = !b.settingsMicCamAccessSwitch.isChecked + } + + b.settingsMicCamAccessSwitch.setOnCheckedChangeListener { _: CompoundButton, checked: Boolean + -> + if (!checked) { + b.settingsMicCamAccessSwitch.isChecked = false + persistentState.micCamAccess = false + return@setOnCheckedChangeListener + } + + // check for the permission and enable the switch + handleAccessibilityPermission() + } + } + + private fun handleAccessibilityPermission() { + try { + val isAccessibilityServiceRunning = + Utilities.isAccessibilityServiceEnabled( + this, + BackgroundAccessibilityService::class.java + ) + val isAccessibilityServiceEnabled = + Utilities.isAccessibilityServiceEnabledViaSettingsSecure( + this, + BackgroundAccessibilityService::class.java + ) + val isAccessibilityServiceFunctional = + isAccessibilityServiceRunning && isAccessibilityServiceEnabled + + if (isAccessibilityServiceFunctional) { + persistentState.micCamAccess = true + b.settingsMicCamAccessSwitch.isChecked = true + return + } + + showPermissionAlert() + b.settingsMicCamAccessSwitch.isChecked = false + persistentState.micCamAccess = false + } catch (e: PackageManager.NameNotFoundException) { + Logger.e(LOG_TAG_APP_OPS, "error checking usage stats permission ${e.message}", e) + return + } + } + + private fun checkMicCamAccessRule() { + if (!persistentState.micCamAccess) return + + val running = + Utilities.isAccessibilityServiceEnabled( + this, + BackgroundAccessibilityService::class.java + ) + val enabled = + Utilities.isAccessibilityServiceEnabledViaSettingsSecure( + this, + BackgroundAccessibilityService::class.java + ) + + Logger.d(LOG_TAG_APP_OPS, "cam/mic access - running: $running, enabled: $enabled") + + val isAccessibilityServiceFunctional = running && enabled + + if (!isAccessibilityServiceFunctional) { + persistentState.micCamAccess = false + b.settingsMicCamAccessSwitch.isChecked = false + showToastUiCentered( + this, + getString(R.string.accessibility_failure_toast), + Toast.LENGTH_SHORT + ) + return + } + + if (running) { + b.settingsMicCamAccessSwitch.isChecked = persistentState.micCamAccess + return + } + } + + private fun showPermissionAlert() { + val builder = MaterialAlertDialogBuilder(this) + builder.setTitle(R.string.alert_permission_accessibility) + builder.setMessage(R.string.alert_firewall_accessibility_explanation) + builder.setPositiveButton(getString(R.string.univ_accessibility_dialog_positive)) { _, _ -> + openAccessibilitySettings() + } + builder.setNegativeButton(getString(R.string.univ_accessibility_dialog_negative)) { _, _ -> + } + builder.setCancelable(false) + builder.create().show() + } + + private fun openAccessibilitySettings() { + try { + val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS) + startActivity(intent) + } catch (e: ActivityNotFoundException) { + showToastUiCentered( + this, + getString(R.string.alert_firewall_accessibility_exception), + Toast.LENGTH_SHORT + ) + Logger.e(LOG_TAG_APP_OPS, "Failure accessing accessibility settings: ${e.message}", e) + } } private fun openConsoleLogActivity() { - val intent = Intent(this, ConsoleLogActivity::class.java) - startActivity(intent) + try { + val intent = Intent(this, ConsoleLogActivity::class.java) + startActivity(intent) + } catch (e: Exception) { + Logger.e(LOG_TAG_UI, "error opening console log activity ${e.message}", e) + } } private fun invokeChangeLocaleDialog() { @@ -577,6 +693,7 @@ class MiscSettingsActivity : AppCompatActivity(R.layout.activity_misc_settings) super.onResume() // app notification permission android 13 showEnableNotificationSettingIfNeeded() + checkMicCamAccessRule() } private fun registerForActivityResult() { diff --git a/app/src/full/java/com/celzero/bravedns/util/BackgroundAccessibilityService.kt b/app/src/full/java/com/celzero/bravedns/util/BackgroundAccessibilityService.kt index ca83ac169..f30eaab58 100644 --- a/app/src/full/java/com/celzero/bravedns/util/BackgroundAccessibilityService.kt +++ b/app/src/full/java/com/celzero/bravedns/util/BackgroundAccessibilityService.kt @@ -16,29 +16,284 @@ package com.celzero.bravedns.util import Logger +import Logger.LOG_TAG_APP_OPS import Logger.LOG_TAG_FIREWALL +import android.Manifest import android.accessibilityservice.AccessibilityService +import android.app.NotificationChannel +import android.app.NotificationManager +import android.app.PendingIntent import android.content.Intent import android.content.pm.PackageManager +import android.graphics.Color +import android.graphics.PixelFormat +import android.hardware.camera2.CameraManager +import android.media.AudioManager +import android.media.AudioRecordingConfiguration +import android.os.Build import android.text.TextUtils +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.WindowManager import android.view.accessibility.AccessibilityEvent +import androidx.annotation.RequiresApi +import androidx.core.app.ActivityCompat +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import com.celzero.bravedns.R +import com.celzero.bravedns.databinding.MicCamAccessIndicatorBinding import com.celzero.bravedns.service.FirewallManager import com.celzero.bravedns.service.PersistentState import com.celzero.bravedns.service.VpnController +import com.celzero.bravedns.ui.HomeScreenActivity +import com.celzero.bravedns.util.Utilities.isAtleastN +import com.celzero.bravedns.util.Utilities.isAtleastP import com.celzero.bravedns.util.Utilities.isAtleastT import org.koin.android.ext.android.inject import org.koin.core.component.KoinComponent +// cam and mic access is still not working as expected, need to test it +// commented out the ui code for now, will enable it once the feature is working +// for cam and mic access ref: github.com/NitishGadangi/Privacy-Indicator-App/blob/master/app/src/main/java/com/nitish/privacyindicator +// see: developer.android.com/guide/topics/media/camera#kotlin +// see: developer.android.com/guide/topics/media/audio-capture class BackgroundAccessibilityService : AccessibilityService(), KoinComponent { private val persistentState by inject() + private lateinit var windowManager: WindowManager + private lateinit var b: MicCamAccessIndicatorBinding + private lateinit var lp: WindowManager.LayoutParams + + private var cameraManager: CameraManager? = null + private var audioManager: AudioManager? = null + private var micCallback: AudioManager.AudioRecordingCallback? = null + private var cameraCallback: CameraManager.AvailabilityCallback? = null + + private var cameraOn = false + private var micOn = false + private var notifManager: NotificationManagerCompat? = null + private var notifBuilder: NotificationCompat.Builder? = null + private var possibleUid: Int? = null + private var possibleAppName: String? = null + private val notificationID = 7897 + + companion object { + private const val NOTIF_CHANNEL_ID = "MIC_CAM_ACCESS" + } + + override fun onServiceConnected() { + if (isAtleastN() && persistentState.micCamAccess) { + overlay() + callBacks() + } + } + + @RequiresApi(Build.VERSION_CODES.N) + private fun callBacks() { + if (!persistentState.micCamAccess) return + + try { + if (cameraManager == null) cameraManager = + getSystemService(CAMERA_SERVICE) as CameraManager + cameraManager!!.registerAvailabilityCallback(getCameraCallback(), null) + + if (audioManager == null) audioManager = getSystemService(AUDIO_SERVICE) as AudioManager + audioManager!!.registerAudioRecordingCallback(getMicCallback(), null) + } catch (e: Exception) { + Logger.e(LOG_TAG_FIREWALL, "Error in registering callbacks: ${e.message}") + } + } + + private fun getCameraCallback(): CameraManager.AvailabilityCallback { + cameraCallback = + object : CameraManager.AvailabilityCallback() { + override fun onCameraAvailable(cameraId: String) { + super.onCameraAvailable(cameraId) + cameraOn = false + hideCam() + dismissNotification() + } + + override fun onCameraUnavailable(cameraId: String) { + super.onCameraUnavailable(cameraId) + cameraOn = true + showCam() + showNotification() + } + } + return cameraCallback as CameraManager.AvailabilityCallback + } + + private fun getMicCallback(): AudioManager.AudioRecordingCallback { + micCallback = + @RequiresApi(Build.VERSION_CODES.N) + object : AudioManager.AudioRecordingCallback() { + override fun onRecordingConfigChanged(configs: List) { + if (configs.isNotEmpty()) { + micOn = true + showMic() + showNotification() + } else { + micOn = false + hideMic() + dismissNotification() + } + } + } + return micCallback as AudioManager.AudioRecordingCallback + } + + private fun overlay() { + windowManager = getSystemService(WINDOW_SERVICE) as WindowManager + lp = WindowManager.LayoutParams() + lp.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY + lp.format = PixelFormat.TRANSLUCENT + lp.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + lp.width = WindowManager.LayoutParams.WRAP_CONTENT + lp.height = WindowManager.LayoutParams.WRAP_CONTENT + lp.gravity = layoutGravity + b = MicCamAccessIndicatorBinding.inflate(LayoutInflater.from(this)) + windowManager.addView(b.root, lp) + } + + private fun showMic() { + Logger.e(LOG_TAG_APP_OPS, "Mic is being used: ${persistentState.micCamAccess}") + if (persistentState.micCamAccess) { + updateIndicatorProperties() + b.ivMic.visibility = View.VISIBLE + } + } + + private fun hideMic() { + b.ivMic.visibility = View.GONE + } + + private fun showCam() { + Logger.e(LOG_TAG_APP_OPS, "Camera is being used: ${persistentState.micCamAccess}") + if (persistentState.micCamAccess) { + updateIndicatorProperties() + b.ivCam.visibility = View.VISIBLE + } + } + + private fun hideCam() { + b.ivCam.visibility = View.GONE + } + + + private val layoutGravity: Int + get() = Gravity.TOP or Gravity.END + + private fun updateIndicatorProperties() { + updateLayoutGravity() + } + + private fun updateLayoutGravity() { + lp.gravity = layoutGravity + windowManager.updateViewLayout(b.root, lp) + } + + private fun setupNotification() { + createNotificationChannel() + notifBuilder = + NotificationCompat.Builder(applicationContext, NOTIF_CHANNEL_ID) + .setSmallIcon(R.drawable.ic_notification_icon) + .setContentTitle(notificationTitle) + .setContentText(notificationDescription) + .setContentIntent(getPendingIntent()) + .setOngoing(true) + .setPriority(NotificationCompat.PRIORITY_HIGH) + notifManager = NotificationManagerCompat.from(applicationContext) + } + + private val notificationTitle: String + get() { + if (cameraOn && micOn) return "Your Camera and Mic is ON" + if (cameraOn && !micOn) return "Your Camera is ON" + return if (!cameraOn && micOn) "Your MIC is ON" else "Your Camera or Mic is ON" + } + + private val notificationDescription: String + get() { + if (cameraOn && micOn) + return "A third-party app($possibleAppName) is using your Camera and Microphone" + if (cameraOn && !micOn) return "A third-party app($possibleAppName) is using your Camera" + return if (!cameraOn && micOn) "A third-party app($possibleAppName) is using your Microphone" + else "A third-party app($possibleAppName) is using your Camera or Microphone" + } + + private fun showNotification() { + setupNotification() + if (ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) != + PackageManager.PERMISSION_GRANTED) { + // notification permission request and handling are done in the HomeScreenFragment + // so no need to handle it here + return + } + if (notifManager != null) + notifManager!!.notify(notificationID, notifBuilder!!.build()) + } + + private fun dismissNotification() { + if (cameraOn || micOn) { + showNotification() + } else { + if (notifManager != null) notifManager!!.cancel(notificationID) + } + } + + private fun getPendingIntent(): PendingIntent { + val intent = Intent(applicationContext, HomeScreenActivity::class.java) + return PendingIntent.getActivity( + applicationContext, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) + } + + private fun createNotificationChannel() { + val notificationChannel = "Notifications for Camera and Mic access" + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val importance = NotificationManager.IMPORTANCE_LOW + val channel = + NotificationChannel(NOTIF_CHANNEL_ID, notificationChannel, importance) + val description = "Notification for Camera and Mic access" + channel.description = description + channel.lightColor = Color.RED + val notificationManager = + applicationContext.getSystemService(NOTIFICATION_SERVICE) as NotificationManager + notificationManager.createNotificationChannel(channel) + } + } override fun onInterrupt() { Logger.w(LOG_TAG_FIREWALL, "BackgroundAccessibilityService Interrupted") } - override fun onRebind(intent: Intent?) { - super.onRebind(intent) + private fun unRegisterCameraCallBack() { + try { + if (cameraManager != null && cameraCallback != null) { + cameraManager!!.unregisterAvailabilityCallback(cameraCallback!!) + } + } catch (e: Exception) { + Logger.e(LOG_TAG_FIREWALL, "Error in unregistering camera callback: ${e.message}") + } + } + + private fun unRegisterMicCallback() { + try { + if (isAtleastN()) { + if (audioManager != null && micCallback != null) { + audioManager!!.unregisterAudioRecordingCallback(micCallback!!) + } + } + } catch (e: Exception) { + Logger.e(LOG_TAG_FIREWALL, "Error in unregistering mic callback: ${e.message}") + } + } + + override fun onDestroy() { + unRegisterCameraCallBack() + unRegisterMicCallback() + super.onDestroy() } override fun onAccessibilityEvent(event: AccessibilityEvent) { @@ -112,14 +367,14 @@ class BackgroundAccessibilityService : AccessibilityService(), KoinComponent { // no need ot handle the events when the vpn is not running if (!VpnController.isOn()) return - if (!persistentState.getBlockAppWhenBackground()) return + if (!persistentState.getBlockAppWhenBackground() && !persistentState.micCamAccess) return val latestTrackedPackage = getEventPackageName(event) if (latestTrackedPackage.isNullOrEmpty()) return val hasContentChanged = - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) { + if (isAtleastP()) { event.eventType == AccessibilityEvent.CONTENT_CHANGE_TYPE_PANE_DISAPPEARED || event.eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED } else { @@ -127,14 +382,17 @@ class BackgroundAccessibilityService : AccessibilityService(), KoinComponent { } Logger.d( LOG_TAG_FIREWALL, - "onAccessibilityEvent: ${event.packageName}, ${event.eventType}, $hasContentChanged" - ) + "onAccessibilityEvent: ${event.packageName}, ${event.eventType}, $hasContentChanged") if (!hasContentChanged) return // If the package received is Rethink, do nothing. if (event.packageName == this.packageName) return + possibleUid = getEventUid(latestTrackedPackage) ?: return + + possibleAppName = Utilities.getPackageInfoForUid(this, possibleUid!!)?.firstOrNull() + // https://stackoverflow.com/a/27642535 // top window is launcher? try revoke queued up permissions // FIXME: Figure out a fool-proof way to determine is launcher visible @@ -142,7 +400,7 @@ class BackgroundAccessibilityService : AccessibilityService(), KoinComponent { if (isPackageLauncher(latestTrackedPackage)) { FirewallManager.untrackForegroundApps() } else { - val uid = getEventUid(latestTrackedPackage) ?: return + val uid = possibleUid ?: return FirewallManager.trackForegroundApp(uid) } } @@ -183,9 +441,7 @@ class BackgroundAccessibilityService : AccessibilityService(), KoinComponent { .resolveActivity( intent, PackageManager.ResolveInfoFlags.of( - PackageManager.MATCH_DEFAULT_ONLY.toLong() - ) - ) + PackageManager.MATCH_DEFAULT_ONLY.toLong())) ?.activityInfo ?.packageName } else { diff --git a/app/src/full/res/layout/activity_misc_settings.xml b/app/src/full/res/layout/activity_misc_settings.xml index bde3caed1..cebb7dd82 100644 --- a/app/src/full/res/layout/activity_misc_settings.xml +++ b/app/src/full/res/layout/activity_misc_settings.xml @@ -865,6 +865,64 @@ + + + + + + + + + + + + + + +
diff --git a/app/src/main/java/com/celzero/bravedns/service/PersistentState.kt b/app/src/main/java/com/celzero/bravedns/service/PersistentState.kt index ed92585e3..4759c49b5 100644 --- a/app/src/main/java/com/celzero/bravedns/service/PersistentState.kt +++ b/app/src/main/java/com/celzero/bravedns/service/PersistentState.kt @@ -290,6 +290,9 @@ class PersistentState(context: Context) : SimpleKrate(context), KoinComponent { // TODO: do we need this? instead use in-memory variable var consoleLogEnabled by booleanPref("console_log_enabled").withDefault(false) + // camera and mic access + var micCamAccess by booleanPref("mic_camera_access").withDefault(false) + var orbotConnectionStatus: MutableLiveData = MutableLiveData() var median: MutableLiveData = MutableLiveData() var vpnEnabledLiveData: MutableLiveData = MutableLiveData() diff --git a/app/src/main/java/com/celzero/bravedns/util/Logger.kt b/app/src/main/java/com/celzero/bravedns/util/Logger.kt index f3e3ec628..e79f9d6db 100644 --- a/app/src/main/java/com/celzero/bravedns/util/Logger.kt +++ b/app/src/main/java/com/celzero/bravedns/util/Logger.kt @@ -42,6 +42,7 @@ object Logger : KoinComponent { const val LOG_TAG_PROXY = "ProxyLogs" const val LOG_QR_CODE = "QrCodeFromFileScanner" const val LOG_GO_LOGGER = "LibLogger" + const val LOG_TAG_APP_OPS = "AppOpsService" // github.com/celzero/firestack/blob/bce8de917f/intra/log/logger.go#L76 enum class LoggerType(val id: Long) { diff --git a/app/src/main/res/drawable/ic_camera.png b/app/src/main/res/drawable/ic_camera.png new file mode 100644 index 0000000000000000000000000000000000000000..1250ecda683accdebdc60101bccd40841145555e GIT binary patch literal 4759 zcmeHL=Tp>8vz-m>l7mPPB}WBh2@)4skf#h3_+8@#{2|#rc!+{gc@mat{-^>VrP%!|YC;;}3L(n_`0T=)l9RW~% z3IG>Ar|~NG7(n|Q80nyAX?Pgv&}uIS(g1*eHZ`=+Ifnkb{>#AsCkDJ^l6a2mS@$V7@z-ek-($>+{(>J($#n8yu#Pq6}xy7~XmR2{cZEWov9G#q9T-|PZ z-1hYH_QBt|>*pU37!({58g}nK0g8-@j){wZ_$VRqaZ++h>XWqejLfIcva)k>^N0n{ zUlhJ9DlRE4E3f#cva0%Z&70b~`i91)=9YJ@q_+3%9i3g>Js&>ye(LKV7##Zivk9Owym>_(ZuJExjns6wq0M zY>#a&inx9~GCA;xMMv?YztFS6>zwq_nWmNYY2vaUezqYE`}6T!VZqnre&tNc*SKfY zOgm7vXF7BKHV@=HTl*Ar^Bppxr|^jq?u~)j`%$TPF)_C?J{XJT=Xe!(G$8ehhc)P4 z- z3Tt^>QPmfX#<;*_j>IDEfB3J!fA%POpvSU>W&P+3crT;z5}Bs49I)Z;>OC~wE=8O4 zdD-#!)|)2xw#tWJ4KdeydYl_FJQm=*tCRwVREfCzSw8y7MEct$y7S7}YyOJpV2V$- zky^!%Rgi6toy-hV*DGnDvDAMVWO}*3u2OkirEW(9A7=Ufq?(k|H*ZDwxBfeozv^<* z1bjuu2fr85+1pPs)xQk%{x<%Nb$h&tGE&%dFQh8QLrmxPbA~ZTLTSs(K$*Fiv_J1( z8QEyQL6PZQ+d?J>!^)n*gHs>U^c_25+j-1qs?Tbn4^5qu=#nhsN;> zt$Z#}rT05_Lx!OZfy(j|G`fDW z+y=#Jf$630usB*P!Kw9ixa^9M7C#19gipIKe}uazJvBNsxJEu)kRG1idcHK7f%Q#g z|Bm2d{fXdKc1=;PgJC4rlfp&)T(E(6pZ~!kXysoXGKlO^K<@RrI96WmND~%FRwN@u z^a?V^gW!$FZ-_m7JvX9F^ySrcah&_ybF1R4Fw|a0NmyF#L?&@b!&4~v>4KC$T;@g- z)Lz9)`W?4rLzfe~a)M8cZd28jnkYFWjjQyEGNh;)1iv52e}u2KPhrT09_m9&psqrb z=O5Dw&*-HST_hUJ;KpOFF^PnCM8vrWq!5S1oN;E~h&551_6yS5yYd^az#9u^1YTta zU(lD4Xxo~Ot6SMtWwf=O0;u)&lg?yv8ow?0Ppg6n>}+ms(lIhIZB-u%aj}DVYkr@& zEBEeyExWcfHQut{5-{SBXObWvwe&UBi3K;qp?b$>E`xf_h;UAc7rBM1Cj&%PN_AWOmEp{b(-*S^gS$G;9!a3VbDtf3s0We{Ygr z%0E-iqYEt9PNBUgpwU2#u+{}EFdcj9rnf?l7z$TtFL{~64~5Q@$p`9QXp$}$E6Y6t zNqKpQCJ}ucW`{A)*b&5gzcT#SZR*z~4E_}4a6m%0sWgWLj~Z*Q(ibCfe~Z#DjSdf5 z=FMGE;RhbUt28{Rc0Ej)s;9v<+aaT1w&rY|JLi7EiAI%BU7;(iM_SK2ps0Hjhl%Vh1-_;S1tz1j;YF>gUZv+6&c*2!9{c2*&&2w zz1!dXxq%Mc>|M(WZ3yesaXMK9{44L;QXBj_%`#e@4-?{KS)`o14mke0Wk?1oze;H? zYiEaM79QCyPKV9@97*tC@fbAfC5LX(7q`!Fn$khOAMhbt<0z%&wAIQ3esF_+2|Z|YbRE0IUG$Jpp0|Bz z*W~$0HBcZbm45ivEh*l7?WO#Rhm#sdl{~i+SY*s#Bi087D1UqZ-tyaPg~-Q@7wmzA z3x{8#FaHIP2CiaR-j+UF`}CCe^pIlqTXeHM_UmMZ<=n;Er+=l)r?2o-0PXNjtI3fA zyiIuK`n@@i*;3jZP$I`|vQ_ItFXmN8F-kx-a+PD?m{nzeY6bFfQac*DEo**ow0E=V zxTn0{F_+R2QLKSb>CrTsG1=q0+S9H`gi`}Z7Ljc(o%&(E!g9+{y`REjYm({+C;hsB zI$F#2588({6cgGh>~pteVUD|JY(Hew@+vc|IKo^6Nf8;TJ*K_$OQf{vNYC@^`R zg2P|D91K!bNau+|hB#S2VZ1<&FxwfZTh0-o?hPSGc%I!jH@-;3`T)9%K%b5W5)2Wt zFYXcAU{SofR2ob;VqXi1W(`5jBMoC|990K7N_b?HA%i|5{A0KyY#nC*cMNNQ2T2dE zBf{!V40=i4DqJAg-U6G^r1*8ob?_5zY0LSM21N`xwRM!4_W(_a?j;sCSF~}G-acCg zpRm88KpEpY;4Z78A@G+Y_#8%juXUf6q95Yy2N}h z?zQ~pe&wR9nn6N9mdp*QJE!G>ZQ-JQw;EW^@4GRHfr%4Yq}t`m95)TU8D;rob<{IRiM1ww!Gh|us?v8AVOl6Z`=I$6w(BC*B~Xr+d%@7s5dqPCj}x^^=jy!ldV|@^+8efY1zkRtjlfaR<>wkmc7sBxjJ=LqXoN zPkQdYEA=a+CWQ^w-`~Gqtl^Oag%KO`g#l7kQnhWuoA$Sz-d>w+i0GgP1v!KVZ@Z|O zn<@lUSG`QN$hT(M=_2oj#|=2aTWWsk3;RlvpK|76LVt^EOt_z2UeTkS01e_6H5OT` zt=4j}+r8HY&c6MaWm6m4h*Sd|aZ-P-ScEQW=)2g8Gg_IN4csp_a~Pq8Yk;3*b%myF zTd9F~nhpl6v)@Mt zSf2?lEerjwleQ;w{*C0w&mPZ!+8ZnLTlMavL2V7fu;lxaDQveK7WAB1fFP`CTW$Al zz_1Lvfd*dovpx0kJNx`EQaB9f7wJKA`Sb=I=NGYg>Ea9r(AnoPp-3oS9* zv{>m^^KVS<|7tyALx~I55YH+ZBvI1j)L8v9USi8c=zgIw^Cyu-_sFi<2SqIAaJRv8 z0{6!3Za{Yy*!ug3zkAGFAjX*=cl4VLLlP2t)wk7~_SQ^hyArpE#EWO1d>KE{_d@nj z-8K0_)A_QgI*GZfOj}z&c10YD(;BZn)_6jO?C4P+-Wn zT{8GBml{qRZ#$>&jcxfge5ZNmi&M3bsSUlmkyZ>|@sjk!C~ky7U1l>e~f z208qUj!Dd2MI$f~hYI*gUoyD`mi$pX?%A6X=?HyRCFquxm=7?E^kSZ(yC!Oww0-Sg@Ppaiw`AJou=(8< zGm9tBhXr5pCs%Tq9ObM0{BvoiA@t6CWj=}=rG0KBjwCp5{Q~&57Q#2PV(Sm)n!#M6 zexX$Obd@*CZhzSAjN)|aYW0<=T`%(J=_}5Yv8G1^y+vtE3+u;!r$VCsI#&L!PX2DH zE_dCI9gxGw$x34`NXsc&$jYf=R8%oak{FCC26OYS((wOE@b-1}xEb<)CveFm`5Y$z N107@SS6Ysd{{wQbOxFMa literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_microphone.png b/app/src/main/res/drawable/ic_microphone.png new file mode 100644 index 0000000000000000000000000000000000000000..460d502ef0ce4d683c0b8065d09858bf17ca3625 GIT binary patch literal 4585 zcmeHKMNk|JlO5dM-C=^x;O;hq1^;mO5C~2f++7l!;FiD_2qAcINN{%>9EK1WG#PBU zZq@E>Pg{RqUQ4~h>+0(2PJF4WL4;3_4*&p&AeyQM&sYKgpaF0(p1p!7qw{CLbWze# z0sy|G6WrTjJ;$sLng%)mz*}wrAR-0;Ks~1-jsO6E5CDL%1puV;000{Aq7Hr8=K_u& zL`My09}A68oKqcBRTTh0Uw^4>r1o6;kNu|u|1Ud0p)ld_EbP+HK-Um}hK_-Wg^h!Y zhfhFAL`*_TMovLVMNLCXN6)~>#LU9V#=*(O%>#VF%f~MuC@qB!dxJ_#0i5&VXl%%Rfiza4-g23|Gqy&QIBst!+)f?bMN?Tb=f@k!q zpaAbqe_J!CjW3DMwG|k}$IC9Lh|76RLb$xE?`y+l3^BgC>IzAVcYyhyjMd%#?!2{o zU{-3hWryJK(ByBO9~~WCwI~ccI11`AakB6Df->vkr}l={#;h|~R+F1flX>of+QRJB z6GM8Ji!MyaJ^$U8a==*UD#1@%0`Hx8?$giaxsA4lGRxsy z=R)R|PZjLbp4aA_nPZ;A7ym}6YQ(;~niA{+1Cv0;PWx~_pQ9{A#r$ndHa@;}b0;r3 z9?*w2W=o&DoiUC>6H7qmOl8b+Z1P+ua3;jO`q%72N4AavL|X1r8o@DlYY5HSpMZx{9kM0UGYjVS^iOQaRce|e3f$^;v0 zi!gZ%&GC}1EN2$;2X2j6j1c-Bp!*v(W7eET-kAq6v4-Qd-?KJi^ZoHgJqAlbvpz%!N;UTzK}BKTcyAWlIa%}w6q zT`m*1X_p~Id*R;tqv|P*cz4g<2+V>Ww*I}xUFIpeRg-~cwe{NNkH*#sl(PiehSArb z(vFcxK5Q?Wgh7^=hBT=^-CxE>t~Vh8yZC9y68RgyHB_Mr0-Cq-*O7;ErGCe^J z#dOo^Wl)Xc5x2s5_Qv^l;-bS~>J*57PTqU_7{43qRbUUY{QR_pr zRul`FUS)3X`sPd2nwoMZwMn7@0W!sZM zk9kwtlDmX=zJHkyK|n7f<0-sR*y#BmI525xj9&(6VB@fkXn(I#fbhf-H=rw1sdj8@ zOxeQ=Oz6qL`rjrpW1Z>1h(lH~eYfue#UED)Eb~1c< zivvQIb8{d|KtVSwdCd*T+G2v8+v`+n3@nRDUb2B>q($$96KNf}A4v*Et_JGu!gsF$ z+j@1JMZI~lYYdy^I7Q(o9K&NGrI~r0#=8r$9idRcsCI;^B%{dvIhOJ&RJ4;gtyk-c zvn9?Zs-oew_)h0X8(Q(ZR=M&H)^}w7N(wQs@@mdSKawPSEz+iv8WbC%$Z`7B2PU>+ zV)+R~3{d%b@>N>i=VMY1<*+%5F_^MJNrtG6u*V26ONv$TFk-FZTG=dE_{HUuH>ed9 z-OOx=Rhm!H#D>vVa}?8srv=DxJ>~7(nRe9QP9VeBdDZNTIq%!Bxzf#Ylm7Ysm#nm6 zY}Ge-X#$R-0`BuWft8OgJo_mSoZD>hBIWdQp=l~UvPUM9sJOz_Z)sI8ypMA#IvhqF zvij4iVrm_U&*IB}Dplh{q|_Jgk(BT6CRsZ&=GHV`gyWLxye3qa6J86QFX+&r2{R{W z0D~lfKwyxq>aVO4!IyksiNi94k2NSSLPupY?W?u-UJVBojAK;ih-S%jy({=@`DPGD zD4ox3AGZ7^LAa6BYul%&t|~?1KQYp?K6Ma9l!;JKVmJrB|%Or3& z;L?WN;En*19Z4W=_4!TR#{fdHhV#aErH{KIc(C}ED-$oY|)sr2lWbKdYu6B+6u6&ut}I+diFTLNmw)r*7b z_Rq$?d4c!p#p!OU>z5n(q*}dMLKJr?W&nK5csR?!%*a{lgWa-0F`bik;({lYESZvx zeQh_twn`;aHgcGfI9PC?TPVo&9$2s#kF}-NK`V1HK#lYpL5Y$MfB!r{{7PWu z)yLyw_ZXVgRWQFXWkY-H0ISdfoy*?Gp-#NLv@%fGnXed6JfYm{N(+I5UB3q6R`jp8 ze+6@(K50XKU4+oEsUr)GR9pSp;}SY1c)D~-e)Q*dbOfGN6$?8-eGZ1l_gYF2)*BmE zRn=6}!J9m!KVBX;l+9-bXV(5*F3<|BF=M4p_N+AcsI#Wm(cgH}Ub|hFs(L#DomZSa zb72>q9GCv3A@IH<+eqXA-zV>Iv|Xw>L1;>C^9srL8mQ;BXq6liUvJNa5mR}N{ec7g7n28m@>f@GhjE$Z$D8i)^AOncj*`wc}GcpU>GZ;uPr@S zD`K#YA?v|AQVbUEYG{yINDTV?yE}A1WAbOW`flyasH3|5@jbGt z8R~snG0P!UN3pA(LJO9xow%32`}Rb;eskM4xFDqgWtri3wk`8CUAmi~yOeP- zuB=OEZZxIFf;dT5UKt5wI(RSd(n;b!rFGco-%ZMP)Lq+XKEVE=y2D#P&_r+5Sa%(k zEucKHFhP<1i>4k?HT0?;cs-=G@GE!Di=Yr*X(dr05EWw8!uKMt6C)t_Q{zWEwQtV- z3)D`WF`wFG724&zGXBO*psFm#GIT5+$Lc{g`5BFID=)g@1y=$dq;y&ims$nr5dSuE z)Vrd8tgt+m_}zIrBsO2BZ)(`}a5fVLZLcs=grV;uW9=E%bnOs!=i$Qw4AsOZjvgG; z7r}!yt!NQm0HhS%|9s2b*6dQq1PV6;dugy%C6oU}b6JwuUQ4--!?sBu!|@dE);Sux zW+u}Ew=Uo3@*|R5Mx-`UxBc(_CaT@lq__C(l21Fkyo>Fe(kX0Xj7=n>_Db)8?h)%f z=EXkT{qULmqb~8acS*KDq#H|s|NI+2JjF|hp(l3kbU($5hYuzrwX5Z{|F`;aYm#K& zllsqVKB<%`9qL`NH?$>qHFDIKr*C#L(wBVGo#d9AYj|q#2yLXkj-q#c%7@DLeMAmg zBah=J3moEScfSv6hn)53!{D$m%>8%IKN6Rp+CP4dc79IM4n9uL01yTVg9Sih0wAyv wSXf$2R9Zxw4+N41fp!o4hW;DD)63D#Irx7kWOa@QKNA2DHC@%u%C=Ge1?~n@&j0`b literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/mic_cam_access_indicator.xml b/app/src/main/res/layout/mic_cam_access_indicator.xml new file mode 100644 index 000000000..0a60ef239 --- /dev/null +++ b/app/src/main/res/layout/mic_cam_access_indicator.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + \ No newline at end of file From d565a7e95d42db59faff03cbdfa457ecc1871e1a Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 29 Jul 2024 18:14:45 +0530 Subject: [PATCH 099/188] ui: icon change for loop back proxy app settings --- app/src/full/res/layout/activity_tunnel_settings.xml | 3 ++- app/src/main/res/drawable/ic_loop_back_app.xml | 10 ++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/drawable/ic_loop_back_app.xml diff --git a/app/src/full/res/layout/activity_tunnel_settings.xml b/app/src/full/res/layout/activity_tunnel_settings.xml index 93fd9f534..a319f4865 100644 --- a/app/src/full/res/layout/activity_tunnel_settings.xml +++ b/app/src/full/res/layout/activity_tunnel_settings.xml @@ -137,9 +137,10 @@ android:layout_marginStart="5dp" android:layout_marginTop="5dp" android:layout_marginEnd="5dp" + android:alpha="0.5" android:layout_marginBottom="5dp" android:padding="10dp" - android:src="@drawable/ic_firewall_exclude_off" /> + android:src="@drawable/ic_loop_back_app" /> + + From f47ab59b92b96ac23edcb569209ad5b33f9d4b37 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 29 Jul 2024 18:15:31 +0530 Subject: [PATCH 100/188] tunnel: bump firestack version --- app/build.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 51d203d59..373ea0202 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -246,11 +246,11 @@ dependencies { fullImplementation 'com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.9' // from: https://jitpack.io/#celzero/firestack - download 'com.github.celzero:firestack:1044953166@aar' - websiteImplementation 'com.github.celzero:firestack:1044953166@aar' - fdroidImplementation 'com.github.celzero:firestack:1044953166@aar' - // debug symbols for crashlytics - playImplementation 'com.github.celzero:firestack:1044953166:debug@aar' + download 'com.github.celzero:firestack:689a5a3d10@aar' + websiteImplementation 'com.github.celzero:firestack:689a5a3d10@aar' + fdroidImplementation 'com.github.celzero:firestack:689a5a3d10@aar' + // debug symbols + playImplementation 'com.github.celzero:firestack:689a5a3d10:debug@aar' // Work manager implementation('androidx.work:work-runtime-ktx:2.9.0') { From 63da862bc7910f4febb1b6c70ff9522109e22814 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 29 Jul 2024 18:16:08 +0530 Subject: [PATCH 101/188] strings: string literal changes for v055o --- app/src/main/res/values/strings.xml | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 3b54a61c0..f4cbb2ecc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -104,6 +104,7 @@ Matrix Reddit test + protos Rethink is experiencing low memory. System actions may be limited. @@ -131,7 +132,7 @@ \u25b4 🔒 - KB/S + %1$s/S MB 1 @@ -636,9 +637,6 @@ Error creating pcap file, Try again! - Custom Probe IPs - Add custom IPs to probe for network connectivity. - Application Icon @@ -829,6 +827,7 @@ Uldiniad
Poussinou
pjosingh
+ RohitSurwase

🚂 Network engine
alalamav
bemasc
@@ -1176,7 +1175,7 @@ domain is set as blocked in Universal domain rules.

To change this behaviour go to Configure DNS tab.]]>
domain is set as trusted in Universal domain rules.

To change this behaviour go to Configure DNS tab.]]>

To change go to Proxy from Configure screen.]]>
- Private DNS is enabled.

To change this behaviour disable Private DNS from Android Settings screen.]]>
+ Private DNS server that doesn\'t exist.]]> Attention From 2dcb174de113f7905fbde5120eeea730bd7dc91c Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Tue, 30 Jul 2024 14:34:18 +0530 Subject: [PATCH 102/188] ui: minor ui change in wg config screen --- .../ui/activity/TunnelSettingsActivity.kt | 3 - .../ui/activity/WgConfigDetailActivity.kt | 25 +- .../main/res/layout/activity_wg_detail.xml | 414 +++++++++--------- 3 files changed, 232 insertions(+), 210 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/TunnelSettingsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/TunnelSettingsActivity.kt index 0b82e6e49..f82422cdd 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/TunnelSettingsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/TunnelSettingsActivity.kt @@ -592,17 +592,14 @@ class TunnelSettingsActivity : AppCompatActivity(R.layout.activity_tunnel_settin if (isLockdown) { b.settingsActivityVpnLockdownDesc.visibility = View.VISIBLE b.settingsActivityAllowBypassRl.alpha = 0.5f - b.settingsActivityLanTrafficRl.alpha = 0.5f b.settingsActivityExcludeProxyAppsRl.alpha = 0.5f } else { b.settingsActivityVpnLockdownDesc.visibility = View.GONE b.settingsActivityAllowBypassRl.alpha = 1f - b.settingsActivityLanTrafficRl.alpha = 1f b.settingsActivityExcludeProxyAppsRl.alpha = 1f } b.settingsActivityAllowBypassSwitch.isEnabled = !isLockdown b.settingsActivityAllowBypassRl.isEnabled = !isLockdown - b.settingsActivityLanTrafficSwitch.isEnabled = !isLockdown b.settingsActivityLanTrafficRl.isEnabled = !isLockdown b.settingsActivityExcludeProxyAppsSwitch.isEnabled = !isLockdown b.settingsActivityExcludeProxyAppsRl.isEnabled = !isLockdown diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt index c1000cfbd..aeb9e152e 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt @@ -120,6 +120,7 @@ class WgConfigDetailActivity : AppCompatActivity(R.layout.activity_wg_detail) { return } + b.editBtn.text = getString(R.string.rt_edit_dialog_positive).lowercase() b.globalLockdownTitleTv.text = getString( R.string.two_argument_space, @@ -210,15 +211,10 @@ class WgConfigDetailActivity : AppCompatActivity(R.layout.activity_wg_detail) { if (wgInterface == null) { return } + b.configNameText.visibility = View.VISIBLE b.configNameText.text = config.getName() - b.publicKeyText.text = wgInterface?.getKeyPair()?.getPublicKey()?.base64() + b.configIdText.text = getString(R.string.single_argument_parenthesis, config.getId().toString()) - if (wgInterface?.getAddresses()?.isEmpty() == true) { - b.addressesLabel.visibility = View.GONE - b.addressesText.visibility = View.GONE - } else { - b.addressesText.text = wgInterface?.getAddresses()?.joinToString { it.toString() } - } setPeersAdapter() // show dns servers if in one-wg mode if (wgType.isOneWg()) { @@ -238,7 +234,16 @@ class WgConfigDetailActivity : AppCompatActivity(R.layout.activity_wg_detail) { b.dnsServersText.visibility = View.GONE } - // uncomment this if we want to show the dns servers, listen port and mtu + // uncomment this if we want to show the public key, addresses, listen port and mtu + /*b.publicKeyText.text = wgInterface?.getKeyPair()?.getPublicKey()?.base64() + + if (wgInterface?.getAddresses()?.isEmpty() == true) { + b.addressesLabel.visibility = View.GONE + b.addressesText.visibility = View.GONE + } else { + b.addressesText.text = wgInterface?.getAddresses()?.joinToString { it.toString() } + }*/ + /*if (wgInterface?.dnsServers?.isEmpty() == true) { b.dnsServersText.visibility = View.GONE b.dnsServersLabel.visibility = View.GONE @@ -347,7 +352,7 @@ class WgConfigDetailActivity : AppCompatActivity(R.layout.activity_wg_detail) { } private fun setupClickListeners() { - b.interfaceEdit.setOnClickListener { + b.editBtn.setOnClickListener { val intent = Intent(this, WgConfigEditorActivity::class.java) intent.putExtra(WgConfigEditorActivity.INTENT_EXTRA_WG_ID, configId) intent.putExtra(INTENT_EXTRA_WG_TYPE, wgType.value) @@ -361,7 +366,7 @@ class WgConfigDetailActivity : AppCompatActivity(R.layout.activity_wg_detail) { openAppsDialog(proxyName) } - b.interfaceDelete.setOnClickListener { showDeleteInterfaceDialog() } + b.deleteBtn.setOnClickListener { showDeleteInterfaceDialog() } /*b.newConfLayout.setOnClickListener { b.newConfProgressBar.visibility = View.VISIBLE diff --git a/app/src/main/res/layout/activity_wg_detail.xml b/app/src/main/res/layout/activity_wg_detail.xml index b707a57ff..24de9b19f 100644 --- a/app/src/main/res/layout/activity_wg_detail.xml +++ b/app/src/main/res/layout/activity_wg_detail.xml @@ -56,178 +56,6 @@
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> - @@ -257,31 +86,22 @@ android:id="@+id/config_name_text" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentStart="true" - android:layout_centerVertical="true" - android:layout_toStartOf="@id/interface_edit" + android:paddingTop="5dp" android:text="" android:textAppearance="?attr/textAppearanceHeadline6" android:textColor="?attr/primaryTextColor" /> - - - + android:layout_gravity="center" + android:layout_marginStart="2dp" + android:layout_marginEnd="3dp" + android:alpha="0.7" + android:maxLength="5" /> - +
@@ -300,6 +121,7 @@ android:ellipsize="end" android:maxLines="1" android:singleLine="true" + android:visibility="gone" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/public_key_label" /> @@ -310,6 +132,7 @@ android:layout_marginTop="8dp" android:labelFor="@+id/config_addresses_text" android:text="@string/lbl_addresses" + android:visibility="gone" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/public_key_text" /> @@ -317,6 +140,7 @@ android:id="@+id/addresses_text" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:visibility="gone" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/addresses_label" /> @@ -410,10 +234,206 @@ app:layout_constraintTop_toBottomOf="@+id/mtu_label" tools:text="1500" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + app:layout_constraintTop_toBottomOf="@+id/other_settings_card" /> @@ -433,7 +453,9 @@ android:layout_height="wrap_content" android:layout_marginEnd="25dp" android:layout_marginBottom="40dp" + android:clickable="true" android:contentDescription="@string/add_peer" + android:focusable="true" android:text="@string/add_peer" android:textColor="?attr/primaryTextColor" app:backgroundTint="?attr/chipColorBgNormal" @@ -441,8 +463,6 @@ app:icon="@drawable/ic_add" app:iconTint="@android:color/transparent" app:iconTintMode="add" - android:clickable="true" - android:focusable="true" app:layout_behavior="com.google.android.material.behavior.HideBottomViewOnScrollBehavior" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" /> From e075c213e8f06d1f834b585e2b770cab94419732 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Tue, 30 Jul 2024 14:36:44 +0530 Subject: [PATCH 103/188] TODO and FIXME for handling lockdown mode and private IP routing --- .../main/java/com/celzero/bravedns/service/BraveVPNService.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt b/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt index a4eef8e26..f6e6eed7e 100644 --- a/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt +++ b/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt @@ -2835,12 +2835,14 @@ class BraveVPNService : } private fun addRoute6(b: Builder): Builder { + // TODO: as of now, vpn lockdown mode is not handled, check if this is required if (persistentState.privateIps) { Logger.i(LOG_TAG_VPN, "addRoute6: privateIps is true, adding routes") // exclude LAN traffic, add only unicast routes // add only unicast routes // range 0000:0000:0000:0000:0000:0000:0000:0000- // 0000:0000:0000:0000:ffff:ffff:ffff:ffff + // fixme: see if the ranges overlap with the default route b.addRoute("0000::", 64) b.addRoute("2000::", 3) // 2000:: - 3fff:: b.addRoute("4000::", 3) // 4000:: - 5fff:: @@ -2867,6 +2869,7 @@ class BraveVPNService : } private fun addRoute4(b: Builder): Builder { + // TODO: as of now, vpn lockdown mode is not handled, check if this is required if (persistentState.privateIps) { Logger.i(LOG_TAG_VPN, "addRoute4: privateIps is true, adding routes") // https://developer.android.com/reference/android/net/VpnService.Builder.html#addRoute(java.lang.String,%20int) From fad09e1361b55f3a93af14ab299dbbf823890593 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Tue, 30 Jul 2024 19:57:25 +0530 Subject: [PATCH 104/188] tunnel: prevent multiple restart on app resume --- .../main/java/com/celzero/bravedns/service/BraveVPNService.kt | 2 ++ app/src/main/java/com/celzero/bravedns/service/PauseTimer.kt | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt b/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt index f6e6eed7e..f6d0fa775 100644 --- a/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt +++ b/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt @@ -2567,11 +2567,13 @@ class BraveVPNService : fun pauseApp() { startPauseTimer() handleVpnServiceOnAppStateChange() + Logger.i(LOG_TAG_VPN, "App paused") } fun resumeApp() { stopPauseTimer() handleVpnServiceOnAppStateChange() + Logger.i(LOG_TAG_VPN, "App resumed") } private fun handleVpnServiceOnAppStateChange() { // paused or resumed diff --git a/app/src/main/java/com/celzero/bravedns/service/PauseTimer.kt b/app/src/main/java/com/celzero/bravedns/service/PauseTimer.kt index 42a3e7e26..65314747b 100644 --- a/app/src/main/java/com/celzero/bravedns/service/PauseTimer.kt +++ b/app/src/main/java/com/celzero/bravedns/service/PauseTimer.kt @@ -51,7 +51,9 @@ object PauseTimer { } } finally { Logger.d(LOG_TAG_VPN, "pause timer complete") - VpnController.resumeApp() + if (VpnController.isAppPaused()) { + VpnController.resumeApp() + } setCountdown(INIT_TIME_MS) } } From a28f25b48e627d243e255b984c9e62798e76d179 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Wed, 31 Jul 2024 16:38:57 +0530 Subject: [PATCH 105/188] ui: change in icon, dialog's padding in network settings screen --- .../bravedns/ui/activity/TunnelSettingsActivity.kt | 10 ++++++++-- .../full/res/layout/activity_tunnel_settings.xml | 2 +- app/src/main/res/drawable/edittext_default.xml | 8 ++++---- app/src/main/res/drawable/ic_loop_back_app.xml | 11 +++++------ app/src/main/res/drawable/ic_loopback.xml | 9 +++++++++ .../main/res/drawable/rethink_within_rethink.xml | 9 --------- app/src/main/res/layout/dialog_input_ips.xml | 13 +++++++------ 7 files changed, 34 insertions(+), 28 deletions(-) create mode 100644 app/src/main/res/drawable/ic_loopback.xml delete mode 100644 app/src/main/res/drawable/rethink_within_rethink.xml diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/TunnelSettingsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/TunnelSettingsActivity.kt index f82422cdd..43568bed1 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/TunnelSettingsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/TunnelSettingsActivity.kt @@ -304,11 +304,14 @@ class TunnelSettingsActivity : AppCompatActivity(R.layout.activity_tunnel_settin val defaultDrawable = ContextCompat.getDrawable(this, R.drawable.edittext_default) val errorDrawable = ContextCompat.getDrawable(this, R.drawable.edittext_error) - val saveBtn: AppCompatButton = dialogView.findViewById(R.id.save_button) + val saveBtn: AppCompatTextView = dialogView.findViewById(R.id.save_button) val testBtn: AppCompatImageView = dialogView.findViewById(R.id.test_button) - val cancelBtn: AppCompatButton = dialogView.findViewById(R.id.cancel_button) + val cancelBtn: AppCompatTextView = dialogView.findViewById(R.id.cancel_button) val resetChip: Chip = dialogView.findViewById(R.id.reset_chip) + saveBtn.text = getString(R.string.lbl_save).uppercase() + cancelBtn.text = getString(R.string.lbl_cancel).uppercase() + val errorMsg: AppCompatTextView = dialogView.findViewById(R.id.error_message) val items4 = persistentState.pingv4Ips.split(",").toTypedArray() @@ -522,6 +525,7 @@ class TunnelSettingsActivity : AppCompatActivity(R.layout.activity_tunnel_settin ) b.settingsActivityPtransRl.visibility = View.GONE b.settingsActivityConnectivityChecksRl.visibility = View.GONE + b.settingsActivityPingIpsBtn.visibility = View.GONE } InternetProtocol.IPv6.id -> { b.genSettingsIpDesc.text = @@ -531,6 +535,7 @@ class TunnelSettingsActivity : AppCompatActivity(R.layout.activity_tunnel_settin ) b.settingsActivityPtransRl.visibility = View.VISIBLE b.settingsActivityConnectivityChecksRl.visibility = View.GONE + b.settingsActivityPingIpsBtn.visibility = View.GONE } InternetProtocol.IPv46.id -> { b.genSettingsIpDesc.text = @@ -540,6 +545,7 @@ class TunnelSettingsActivity : AppCompatActivity(R.layout.activity_tunnel_settin ) b.settingsActivityPtransRl.visibility = View.GONE b.settingsActivityConnectivityChecksRl.visibility = View.VISIBLE + b.settingsActivityPingIpsBtn.visibility = View.VISIBLE } else -> { b.genSettingsIpDesc.text = diff --git a/app/src/full/res/layout/activity_tunnel_settings.xml b/app/src/full/res/layout/activity_tunnel_settings.xml index a319f4865..781142424 100644 --- a/app/src/full/res/layout/activity_tunnel_settings.xml +++ b/app/src/full/res/layout/activity_tunnel_settings.xml @@ -308,7 +308,7 @@ android:layout_marginBottom="5dp" android:alpha="0.5" android:padding="10dp" - android:src="@drawable/rethink_within_rethink" /> + android:src="@drawable/ic_loopback" /> + android:bottom="10dp" + android:left="10dp" + android:right="10dp" + android:top="10dp" /> diff --git a/app/src/main/res/drawable/ic_loop_back_app.xml b/app/src/main/res/drawable/ic_loop_back_app.xml index 0f8f9d7bd..3f433d818 100644 --- a/app/src/main/res/drawable/ic_loop_back_app.xml +++ b/app/src/main/res/drawable/ic_loop_back_app.xml @@ -1,10 +1,9 @@ - + android:viewportWidth="24" + android:viewportHeight="24"> + diff --git a/app/src/main/res/drawable/ic_loopback.xml b/app/src/main/res/drawable/ic_loopback.xml new file mode 100644 index 000000000..167a071c6 --- /dev/null +++ b/app/src/main/res/drawable/ic_loopback.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/rethink_within_rethink.xml b/app/src/main/res/drawable/rethink_within_rethink.xml deleted file mode 100644 index 3f433d818..000000000 --- a/app/src/main/res/drawable/rethink_within_rethink.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/layout/dialog_input_ips.xml b/app/src/main/res/layout/dialog_input_ips.xml index 8c5bc930b..51d5bd6fb 100644 --- a/app/src/main/res/layout/dialog_input_ips.xml +++ b/app/src/main/res/layout/dialog_input_ips.xml @@ -302,25 +302,26 @@ android:layout_margin="5dp" android:orientation="horizontal"> - - From 04c9adc2e2cbd42638905dbd00864e1515ed482f Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Fri, 9 Aug 2024 17:15:30 +0530 Subject: [PATCH 106/188] tunnel: unlink adapter before restart in lockdown mode only --- .../java/com/celzero/bravedns/service/BraveVPNService.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt b/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt index f6d0fa775..4affea208 100644 --- a/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt +++ b/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt @@ -2068,6 +2068,12 @@ class BraveVPNService : LOG_TAG_VPN, "---------------------------RESTART-INIT----------------------------" ) + // In vpn lockdown mode, unlink the adapter to close the previous file descriptor (fd) + // and use a new fd after creation. This should only be done in lockdown mode, + // as leaks are not possible. + if (VpnController.isVpnLockdown()) { + vpnAdapter?.unlink() + } // attempt seamless hand-off as described in VpnService.Builder.establish() docs val tunFd = establishVpn(networks) if (tunFd == null) { From ff2f09a25fddba217aedb6e13edc9918fcc69677 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Fri, 9 Aug 2024 17:16:50 +0530 Subject: [PATCH 107/188] tunnel: unlink adapter before restart in lockdown mode only --- .../com/celzero/bravedns/net/go/GoVpnAdapter.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/src/main/java/com/celzero/bravedns/net/go/GoVpnAdapter.kt b/app/src/main/java/com/celzero/bravedns/net/go/GoVpnAdapter.kt index a1c879dfd..eb3dd8f2a 100644 --- a/app/src/main/java/com/celzero/bravedns/net/go/GoVpnAdapter.kt +++ b/app/src/main/java/com/celzero/bravedns/net/go/GoVpnAdapter.kt @@ -373,6 +373,19 @@ class GoVpnAdapter : KoinComponent { } } + suspend fun unlink() { + if (!tunnel.isConnected) { + Logger.i(LOG_TAG_VPN, "Tunnel NOT connected, skip unlink") + return + } + try { + tunnel.unlink() + Logger.i(LOG_TAG_VPN, "tunnel unlinked") + } catch (e: Exception) { + Logger.e(LOG_TAG_VPN, "err dispose: ${e.message}", e) + } + } + private suspend fun getRdnsStamp(url: String): String { val s = url.split(RETHINKDNS_DOMAIN)[1] val stamp = From f97d1c88ffd45612418e35ea636bc2288994c7d4 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 10 Aug 2024 19:45:45 +0530 Subject: [PATCH 108/188] ui: launch app info screen on new app notification #1146 --- .../adapter/FirewallAppListAdapter.kt | 7 +-- .../adapter/SummaryStatisticsAdapter.kt | 2 +- .../bravedns/ui/NotificationHandlerDialog.kt | 46 +++++++++++-------- .../bravedns/ui/activity/AppInfoActivity.kt | 9 ++-- .../ui/activity/AppWiseDomainLogsActivity.kt | 2 +- .../ui/activity/AppWiseIpLogsActivity.kt | 2 +- .../ui/bottomsheet/ConnTrackerBottomSheet.kt | 2 +- .../ui/bottomsheet/RethinkLogBottomSheet.kt | 2 +- .../bravedns/database/RefreshDatabase.kt | 1 + 9 files changed, 41 insertions(+), 32 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt index 3a4d536af..b9b284ab0 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt @@ -20,13 +20,11 @@ import android.content.DialogInterface import android.content.Intent import android.content.pm.PackageManager import android.graphics.drawable.Drawable -import android.util.TypedValue import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.ImageView -import android.widget.TextView import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.lifecycle.LifecycleOwner @@ -43,8 +41,7 @@ import com.celzero.bravedns.service.FirewallManager.updateFirewallStatus import com.celzero.bravedns.service.ProxyManager import com.celzero.bravedns.service.ProxyManager.ID_NONE import com.celzero.bravedns.ui.activity.AppInfoActivity -import com.celzero.bravedns.ui.activity.AppInfoActivity.Companion.UID_INTENT_NAME -import com.celzero.bravedns.util.UIUtils +import com.celzero.bravedns.ui.activity.AppInfoActivity.Companion.INTENT_UID import com.celzero.bravedns.util.Utilities import com.celzero.bravedns.util.Utilities.getIcon import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -394,7 +391,7 @@ class FirewallAppListAdapter( private fun openAppDetailActivity(uid: Int) { val intent = Intent(context, AppInfoActivity::class.java) - intent.putExtra(UID_INTENT_NAME, uid) + intent.putExtra(INTENT_UID, uid) context.startActivity(intent) } diff --git a/app/src/full/java/com/celzero/bravedns/adapter/SummaryStatisticsAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/SummaryStatisticsAdapter.kt index 973653022..610d0edbb 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/SummaryStatisticsAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/SummaryStatisticsAdapter.kt @@ -423,7 +423,7 @@ class SummaryStatisticsAdapter( private fun startAppInfoActivity(appConnection: AppConnection) { val intent = Intent(context, AppInfoActivity::class.java) - intent.putExtra(AppInfoActivity.UID_INTENT_NAME, appConnection.uid) + intent.putExtra(AppInfoActivity.INTENT_UID, appConnection.uid) context.startActivity(intent) } diff --git a/app/src/full/java/com/celzero/bravedns/ui/NotificationHandlerDialog.kt b/app/src/full/java/com/celzero/bravedns/ui/NotificationHandlerDialog.kt index 5c370981d..d566aa550 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/NotificationHandlerDialog.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/NotificationHandlerDialog.kt @@ -26,6 +26,8 @@ import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import com.celzero.bravedns.R import com.celzero.bravedns.service.VpnController +import com.celzero.bravedns.ui.activity.AppInfoActivity +import com.celzero.bravedns.ui.activity.AppInfoActivity.Companion.INTENT_UID import com.celzero.bravedns.ui.activity.AppListActivity import com.celzero.bravedns.ui.activity.PauseActivity import com.celzero.bravedns.util.Constants @@ -34,8 +36,8 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder class NotificationHandlerDialog : AppCompatActivity() { enum class TrampolineType { ACCESSIBILITY_SERVICE_FAILURE_DIALOG, - NEW_APP_INSTAL_DIALOG, - HOMESCREEN_ACTIVITY, + NEW_APP_INSTALL_DIALOG, + HOME_SCREEN_ACTIVITY, PAUSE_ACTIVITY, NONE } @@ -55,12 +57,12 @@ class NotificationHandlerDialog : AppCompatActivity() { private fun handleNotificationIntent(intent: Intent) { // app not started launch home screen if (!VpnController.isOn()) { - trampoline(TrampolineType.NONE) + trampoline(TrampolineType.NONE, intent) return } if (VpnController.isAppPaused()) { - trampoline(TrampolineType.PAUSE_ACTIVITY) + trampoline(TrampolineType.PAUSE_ACTIVITY, intent) return } @@ -68,28 +70,28 @@ class NotificationHandlerDialog : AppCompatActivity() { if (isAccessibilityIntent(intent)) { TrampolineType.ACCESSIBILITY_SERVICE_FAILURE_DIALOG } else if (isNewAppInstalledIntent(intent)) { - TrampolineType.NEW_APP_INSTAL_DIALOG + TrampolineType.NEW_APP_INSTALL_DIALOG } else { TrampolineType.NONE } - trampoline(t) + trampoline(t, intent) } - private fun trampoline(trampolineType: TrampolineType) { + private fun trampoline(trampolineType: TrampolineType, intent: Intent) { Logger.i(LOG_TAG_VPN, "act on notification, notification type: $trampolineType") when (trampolineType) { TrampolineType.ACCESSIBILITY_SERVICE_FAILURE_DIALOG -> { handleAccessibilitySettings() } - TrampolineType.NEW_APP_INSTAL_DIALOG -> { + TrampolineType.NEW_APP_INSTALL_DIALOG -> { // navigate to all apps screen - launchFirewallActivityAndFinish() + launchFirewallActivityAndFinish(intent) } - TrampolineType.HOMESCREEN_ACTIVITY -> { + TrampolineType.HOME_SCREEN_ACTIVITY -> { launchHomeScreenAndFinish() } TrampolineType.PAUSE_ACTIVITY -> { - showAppPauseDialog(trampolineType) + showAppPauseDialog(trampolineType, intent) } TrampolineType.NONE -> { launchHomeScreenAndFinish() @@ -102,11 +104,19 @@ class NotificationHandlerDialog : AppCompatActivity() { finish() } - private fun launchFirewallActivityAndFinish() { - val intent = Intent(this, AppListActivity::class.java) - intent.flags = Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED - startActivity(intent) - finish() + private fun launchFirewallActivityAndFinish(recvIntent: Intent) { + val uid = recvIntent.getIntExtra(Constants.NOTIF_INTENT_EXTRA_APP_UID, Int.MIN_VALUE) + Logger.d(LOG_TAG_VPN, "notification intent - new app installed, uid: $uid") + if (uid > 0) { + val intent = Intent(this, AppInfoActivity::class.java) + intent.putExtra(INTENT_UID, uid) + startActivity(intent) + finish() + } else { + val intent = Intent(this, AppListActivity::class.java) + startActivity(intent) + finish() + } } private fun handleAccessibilitySettings() { @@ -131,7 +141,7 @@ class NotificationHandlerDialog : AppCompatActivity() { ContextCompat.startActivity(context, intent, null) } - private fun showAppPauseDialog(trampolineType: TrampolineType) { + private fun showAppPauseDialog(trampolineType: TrampolineType, intent: Intent) { val builder = MaterialAlertDialogBuilder(this) builder.setTitle(R.string.notif_dialog_pause_dialog_title) @@ -141,7 +151,7 @@ class NotificationHandlerDialog : AppCompatActivity() { builder.setPositiveButton(R.string.notif_dialog_pause_dialog_positive) { _, _ -> VpnController.resumeApp() - trampoline(trampolineType) + trampoline(trampolineType, intent) } builder.setNegativeButton(R.string.notif_dialog_pause_dialog_negative) { _, _ -> finish() } diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/AppInfoActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/AppInfoActivity.kt index 810d56967..c61d6f4c5 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/AppInfoActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/AppInfoActivity.kt @@ -84,13 +84,14 @@ class AppInfoActivity : AppCompatActivity(R.layout.activity_app_details) { private var isRethinkApp: Boolean = false companion object { - const val UID_INTENT_NAME = "UID" + const val INTENT_UID = "UID" } override fun onCreate(savedInstanceState: Bundle?) { setTheme(Themes.getCurrentTheme(isDarkThemeOn(), persistentState.theme)) super.onCreate(savedInstanceState) - uid = intent.getIntExtra(UID_INTENT_NAME, INVALID_UID) + uid = intent.getIntExtra(INTENT_UID, INVALID_UID) + Logger.d(Logger.LOG_TAG_UI, "AppInfoActivity, intent uid: $uid") ipRulesViewModel.setUid(uid) domainRulesViewModel.setUid(uid) networkLogsViewModel.setUid(uid) @@ -382,13 +383,13 @@ class AppInfoActivity : AppCompatActivity(R.layout.activity_app_details) { private fun openAppWiseDomainLogsActivity() { val intent = Intent(this, AppWiseDomainLogsActivity::class.java) - intent.putExtra(UID_INTENT_NAME, uid) + intent.putExtra(INTENT_UID, uid) startActivity(intent) } private fun openAppWiseIpLogsActivity() { val intent = Intent(this, AppWiseIpLogsActivity::class.java) - intent.putExtra(UID_INTENT_NAME, uid) + intent.putExtra(INTENT_UID, uid) startActivity(intent) } diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseDomainLogsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseDomainLogsActivity.kt index bb7a5da37..f951f042e 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseDomainLogsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseDomainLogsActivity.kt @@ -70,7 +70,7 @@ class AppWiseDomainLogsActivity : override fun onCreate(savedInstanceState: Bundle?) { setTheme(Themes.getCurrentTheme(isDarkThemeOn(), persistentState.theme)) super.onCreate(savedInstanceState) - uid = intent.getIntExtra(AppInfoActivity.UID_INTENT_NAME, INVALID_UID) + uid = intent.getIntExtra(AppInfoActivity.INTENT_UID, INVALID_UID) if (uid == INVALID_UID) { finish() } diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseIpLogsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseIpLogsActivity.kt index e631ba92f..1667acd74 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseIpLogsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseIpLogsActivity.kt @@ -69,7 +69,7 @@ class AppWiseIpLogsActivity : override fun onCreate(savedInstanceState: Bundle?) { setTheme(Themes.getCurrentTheme(isDarkThemeOn(), persistentState.theme)) super.onCreate(savedInstanceState) - uid = intent.getIntExtra(AppInfoActivity.UID_INTENT_NAME, INVALID_UID) + uid = intent.getIntExtra(AppInfoActivity.INTENT_UID, INVALID_UID) if (uid == INVALID_UID) { finish() } diff --git a/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/ConnTrackerBottomSheet.kt b/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/ConnTrackerBottomSheet.kt index 2aea7bc8c..b8048b17e 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/ConnTrackerBottomSheet.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/ConnTrackerBottomSheet.kt @@ -538,7 +538,7 @@ class ConnTrackerBottomSheet : BottomSheetDialogFragment(), KoinComponent { private fun openAppDetailActivity(uid: Int) { this.dismiss() val intent = Intent(requireContext(), AppInfoActivity::class.java) - intent.putExtra(AppInfoActivity.UID_INTENT_NAME, uid) + intent.putExtra(AppInfoActivity.INTENT_UID, uid) requireContext().startActivity(intent) } diff --git a/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/RethinkLogBottomSheet.kt b/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/RethinkLogBottomSheet.kt index 4b0a71e8a..f2a6fa48c 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/RethinkLogBottomSheet.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/RethinkLogBottomSheet.kt @@ -382,7 +382,7 @@ class RethinkLogBottomSheet : BottomSheetDialogFragment(), KoinComponent { private fun openAppDetailActivity(uid: Int) { this.dismiss() val intent = Intent(requireContext(), AppInfoActivity::class.java) - intent.putExtra(AppInfoActivity.UID_INTENT_NAME, uid) + intent.putExtra(AppInfoActivity.INTENT_UID, uid) requireContext().startActivity(intent) } diff --git a/app/src/main/java/com/celzero/bravedns/database/RefreshDatabase.kt b/app/src/main/java/com/celzero/bravedns/database/RefreshDatabase.kt index 985143a97..c6d34ff12 100644 --- a/app/src/main/java/com/celzero/bravedns/database/RefreshDatabase.kt +++ b/app/src/main/java/com/celzero/bravedns/database/RefreshDatabase.kt @@ -583,6 +583,7 @@ internal constructor( Constants.NOTIF_INTENT_EXTRA_NEW_APP_NAME, Constants.NOTIF_INTENT_EXTRA_NEW_APP_VALUE ) + intent.putExtra(Constants.NOTIF_INTENT_EXTRA_APP_UID, app.uid) val pendingIntent = getActivityPendingIntent( From 3f98079f5be5c66297f57a6cf16880a40f4cf791 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 22 Aug 2024 19:32:26 +0530 Subject: [PATCH 109/188] ui: implement fast scroll for logs and apps screen --- .../bravedns/adapter/FirewallAppListAdapter.kt | 8 +++++++- .../ui/fragment/ConnectionTrackerFragment.kt | 6 +++--- .../bravedns/ui/fragment/RethinkLogFragment.kt | 6 +++--- app/src/full/res/layout/activity_app_list.xml | 13 +++++++++++-- ..._tracker.xml => fragment_connection_tracker.xml} | 13 ++++++++++--- app/src/main/res/layout/fragment_dns_logs.xml | 12 +++++++++--- 6 files changed, 43 insertions(+), 15 deletions(-) rename app/src/full/res/layout/{activity_connection_tracker.xml => fragment_connection_tracker.xml} (92%) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt index b9b284ab0..8296c6918 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt @@ -45,6 +45,7 @@ import com.celzero.bravedns.ui.activity.AppInfoActivity.Companion.INTENT_UID import com.celzero.bravedns.util.Utilities import com.celzero.bravedns.util.Utilities.getIcon import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.simplecityapps.recyclerview_fastscroll.views.FastScrollRecyclerView.SectionedAdapter import java.util.concurrent.TimeUnit import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -53,7 +54,7 @@ import kotlinx.coroutines.withContext class FirewallAppListAdapter( private val context: Context, private val lifecycleOwner: LifecycleOwner -) : PagingDataAdapter(DIFF_CALLBACK) { +) : PagingDataAdapter(DIFF_CALLBACK), SectionedAdapter { private val packageManager: PackageManager = context.packageManager // private val systemAppColor: Int by lazy { UIUtils.fetchColor(context, R.attr.textColorAccentBad) } @@ -464,4 +465,9 @@ class FirewallAppListAdapter( private suspend fun ioCtx(f: suspend () -> Unit) { withContext(Dispatchers.IO) { f() } } + + override fun getSectionName(position: Int): String { + val appInfo = getItem(position) ?: return "" + return appInfo.appName.substring(0, 1) + } } diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/ConnectionTrackerFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/ConnectionTrackerFragment.kt index 9c93dc6a1..6bdba64a0 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/ConnectionTrackerFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/ConnectionTrackerFragment.kt @@ -31,7 +31,7 @@ import by.kirich1409.viewbindingdelegate.viewBinding import com.celzero.bravedns.R import com.celzero.bravedns.adapter.ConnectionTrackerAdapter import com.celzero.bravedns.database.ConnectionTrackerRepository -import com.celzero.bravedns.databinding.ActivityConnectionTrackerBinding +import com.celzero.bravedns.databinding.FragmentConnectionTrackerBinding import com.celzero.bravedns.service.FirewallRuleset import com.celzero.bravedns.service.PersistentState import com.celzero.bravedns.ui.activity.UniversalFirewallSettingsActivity @@ -49,8 +49,8 @@ import org.koin.androidx.viewmodel.ext.android.viewModel /** Captures network logs and stores in ConnectionTracker, a room database. */ class ConnectionTrackerFragment : - Fragment(R.layout.activity_connection_tracker), SearchView.OnQueryTextListener { - private val b by viewBinding(ActivityConnectionTrackerBinding::bind) + Fragment(R.layout.fragment_connection_tracker), SearchView.OnQueryTextListener { + private val b by viewBinding(FragmentConnectionTrackerBinding::bind) private var layoutManager: RecyclerView.LayoutManager? = null private val viewModel: ConnectionTrackerViewModel by viewModel() diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/RethinkLogFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/RethinkLogFragment.kt index 43d232109..e3f867b51 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/RethinkLogFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/RethinkLogFragment.kt @@ -28,7 +28,7 @@ import by.kirich1409.viewbindingdelegate.viewBinding import com.celzero.bravedns.R import com.celzero.bravedns.adapter.RethinkLogAdapter import com.celzero.bravedns.database.RethinkLogRepository -import com.celzero.bravedns.databinding.ActivityConnectionTrackerBinding +import com.celzero.bravedns.databinding.FragmentConnectionTrackerBinding import com.celzero.bravedns.service.PersistentState import com.celzero.bravedns.util.Constants import com.celzero.bravedns.util.UIUtils.formatToRelativeTime @@ -41,8 +41,8 @@ import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel class RethinkLogFragment : - Fragment(R.layout.activity_connection_tracker), SearchView.OnQueryTextListener { - private val b by viewBinding(ActivityConnectionTrackerBinding::bind) + Fragment(R.layout.fragment_connection_tracker), SearchView.OnQueryTextListener { + private val b by viewBinding(FragmentConnectionTrackerBinding::bind) private var layoutManager: RecyclerView.LayoutManager? = null private val viewModel: RethinkLogViewModel by viewModel() diff --git a/app/src/full/res/layout/activity_app_list.xml b/app/src/full/res/layout/activity_app_list.xml index 02cdba569..52827cedb 100644 --- a/app/src/full/res/layout/activity_app_list.xml +++ b/app/src/full/res/layout/activity_app_list.xml @@ -217,7 +217,7 @@ - + app:fastScrollVerticalTrackDrawable="@drawable/fast_scroll_line_drawable" + app:fastScrollAutoHide="true" + app:fastScrollAutoHideDelay="1500" + app:fastScrollEnableThumbInactiveColor="true" + app:fastScrollThumbColor="?attr/chipColorBgNormal" + app:fastScrollThumbEnabled="true" + app:fastScrollThumbInactiveColor="?attr/chipColorBgNormal" + app:fastScrollTrackColor="?attr/background" /> diff --git a/app/src/full/res/layout/activity_connection_tracker.xml b/app/src/full/res/layout/fragment_connection_tracker.xml similarity index 92% rename from app/src/full/res/layout/activity_connection_tracker.xml rename to app/src/full/res/layout/fragment_connection_tracker.xml index 8b01153fe..02ca62d74 100644 --- a/app/src/full/res/layout/activity_connection_tracker.xml +++ b/app/src/full/res/layout/fragment_connection_tracker.xml @@ -22,7 +22,6 @@ android:layout_height="wrap_content" android:orientation="vertical"> - - + android:orientation="vertical" + android:nestedScrollingEnabled="true" + app:fastScrollAutoHide="true" + app:fastScrollAutoHideDelay="1500" + app:fastScrollEnableThumbInactiveColor="true" + app:fastScrollThumbColor="?attr/chipColorBgNormal" + app:fastScrollThumbEnabled="true" + app:fastScrollThumbInactiveColor="?attr/chipColorBgNormal" + app:fastScrollTrackColor="?attr/background" /> - + app:fastScrollAutoHide="true" + app:fastScrollAutoHideDelay="1500" + android:nestedScrollingEnabled="true" + app:fastScrollThumbInactiveColor="?attr/chipColorBgNormal" + app:fastScrollThumbEnabled="true" + app:fastScrollThumbColor="?attr/chipColorBgNormal" + app:fastScrollEnableThumbInactiveColor="true" + app:fastScrollTrackColor="?attr/background" /> - From 5d63fc25a777876e0d21636d62eb4f5f302a6b6f Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 22 Aug 2024 19:37:13 +0530 Subject: [PATCH 110/188] ui: show wireguard status in detail conf screen --- .../ui/activity/WgConfigDetailActivity.kt | 54 +++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt index aeb9e152e..7d9013d20 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt @@ -21,16 +21,20 @@ import android.content.Context import android.content.Intent import android.content.res.Configuration import android.os.Bundle +import android.text.format.DateUtils import android.view.View import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager +import backend.Backend +import backend.RouterStats import by.kirich1409.viewbindingdelegate.viewBinding import com.celzero.bravedns.R import com.celzero.bravedns.adapter.WgConfigAdapter import com.celzero.bravedns.adapter.WgIncludeAppsAdapter import com.celzero.bravedns.adapter.WgPeersAdapter +import com.celzero.bravedns.database.WgConfigFiles import com.celzero.bravedns.databinding.ActivityWgDetailBinding import com.celzero.bravedns.service.PersistentState import com.celzero.bravedns.service.ProxyManager @@ -183,9 +187,56 @@ class WgConfigDetailActivity : AppCompatActivity(R.layout.activity_wg_detail) { return@uiCtx }*/ + io { updateStatusUi(config.getId()) } prefillConfig(config) } + + private suspend fun updateStatusUi(id: Int) { + val config = WireguardManager.getConfigFilesById(id) + val cid = ProxyManager.ID_WG_BASE + id + if (config?.isActive == true) { + var status: String + val statusId = VpnController.getProxyStatusById(cid) + val stats = VpnController.getProxyStats(cid) + uiCtx { + if (statusId != null) { + var resId = UIUtils.getProxyStatusStringRes(statusId) + // change the color based on the status + if (statusId == Backend.TOK) { + // if the lastOK is 0, then the handshake is not yet completed + // so show the status as waiting + if (stats?.lastOK == 0L) { + resId = R.string.status_waiting + } + } + status = getString(resId).replaceFirstChar(Char::titlecase) + + if ((statusId == Backend.TZZ || statusId == Backend.TNT) && stats != null) { + // for idle state, if lastOk is less than 30 sec, then show as connected + if ( + stats.lastOK != 0L && + System.currentTimeMillis() - stats.lastOK < + 30 * DateUtils.SECOND_IN_MILLIS + ) { + status = + getString(R.string.dns_connected).replaceFirstChar(Char::titlecase) + } + } + + } else { + status = getString(R.string.status_waiting).replaceFirstChar(Char::titlecase) + } + b.statusText.text = status + } + } else { + uiCtx { + b.statusText.text = + getString(R.string.lbl_disabled).replaceFirstChar(Char::titlecase) + } + } + } + private fun showInvalidConfigDialog() { val builder = MaterialAlertDialogBuilder(this) builder.setTitle(getString(R.string.lbl_wireguard)) @@ -230,6 +281,9 @@ class WgConfigDetailActivity : AppCompatActivity(R.layout.activity_wg_detail) { } b.dnsServersText.text = dns } else { + b.publicKeyLabel.visibility = View.VISIBLE + b.publicKeyText.visibility = View.VISIBLE + b.publicKeyText.text = wgInterface?.getKeyPair()?.getPublicKey()?.base64() b.dnsServersLabel.visibility = View.GONE b.dnsServersText.visibility = View.GONE } From 80ea2c2dd0573ebf77e1122a62dc026e8631e906 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 22 Aug 2024 19:42:32 +0530 Subject: [PATCH 111/188] ui: show last app lock time in DEBUG mode --- .../ui/bottomsheet/HomeScreenSettingBottomSheet.kt | 14 ++++++++++++++ .../full/res/layout/bottom_sheet_home_screen.xml | 12 ++++++++++++ 2 files changed, 26 insertions(+) diff --git a/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/HomeScreenSettingBottomSheet.kt b/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/HomeScreenSettingBottomSheet.kt index 0f1002e43..a72b43632 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/HomeScreenSettingBottomSheet.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/HomeScreenSettingBottomSheet.kt @@ -18,7 +18,9 @@ package com.celzero.bravedns.ui.bottomsheet import Logger import Logger.LOG_TAG_VPN import android.content.res.Configuration +import android.icu.util.TimeUnit import android.os.Bundle +import android.os.SystemClock import android.text.format.DateUtils import android.view.LayoutInflater import android.view.View @@ -26,12 +28,15 @@ import android.view.ViewGroup import android.widget.CompoundButton import androidx.lifecycle.lifecycleScope import com.celzero.bravedns.R +import com.celzero.bravedns.RethinkDnsApplication.Companion.DEBUG import com.celzero.bravedns.data.AppConfig import com.celzero.bravedns.databinding.BottomSheetHomeScreenBinding import com.celzero.bravedns.service.PersistentState import com.celzero.bravedns.service.VpnController import com.celzero.bravedns.util.Constants.Companion.INIT_TIME_MS import com.celzero.bravedns.util.Themes.Companion.getBottomsheetCurrentTheme +import com.celzero.bravedns.util.UIUtils.formatToRelativeTime +import com.celzero.bravedns.util.UIUtils.getDurationInHumanReadableFormat import com.celzero.bravedns.util.UIUtils.openVpnProfile import com.google.android.material.bottomsheet.BottomSheetDialogFragment import kotlinx.coroutines.Dispatchers @@ -83,6 +88,15 @@ class HomeScreenSettingBottomSheet : BottomSheetDialogFragment() { val selectedIndex = appConfig.getBraveMode().mode Logger.d(LOG_TAG_VPN, "Home screen bottom sheet selectedIndex: $selectedIndex") + if (DEBUG) { + val timeSinceLastAuth = SystemClock.elapsedRealtime() - persistentState.biometricAuthTime + b.bsHomeScreenAppLockTime.visibility = View.VISIBLE + b.bsHomeScreenAppLockTime.text = java.util.concurrent.TimeUnit.MILLISECONDS.toMinutes(timeSinceLastAuth).toString() + " minutes" + Logger.d(LOG_TAG_VPN, "last auth time: ${persistentState.biometricAuthTime}") + } else { + b.bsHomeScreenAppLockTime.visibility = View.GONE + } + updateStatus(selectedIndex) } diff --git a/app/src/full/res/layout/bottom_sheet_home_screen.xml b/app/src/full/res/layout/bottom_sheet_home_screen.xml index 90393f3b8..54f9baf82 100644 --- a/app/src/full/res/layout/bottom_sheet_home_screen.xml +++ b/app/src/full/res/layout/bottom_sheet_home_screen.xml @@ -298,6 +298,18 @@ android:textColor="?attr/primaryTextColor" android:textSize="@dimen/small_font_text_view" /> + + From 2ba0301608608616ec215ebe296ffaeac121d951 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 22 Aug 2024 19:56:55 +0530 Subject: [PATCH 112/188] ui: show wireguard status in detail conf screen --- app/src/main/res/layout/activity_wg_detail.xml | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/layout/activity_wg_detail.xml b/app/src/main/res/layout/activity_wg_detail.xml index 24de9b19f..c13910d0e 100644 --- a/app/src/main/res/layout/activity_wg_detail.xml +++ b/app/src/main/res/layout/activity_wg_detail.xml @@ -103,6 +103,15 @@
+ + + app:layout_constraintTop_toBottomOf="@id/status_text" /> Date: Thu, 22 Aug 2024 20:01:17 +0530 Subject: [PATCH 113/188] ui: drawables for fast scroll --- app/src/main/res/drawable/fast_scoller_bubble.xml | 15 +++++++++++++++ .../main/res/drawable/fast_scroller_handle.xml | 15 +++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 app/src/main/res/drawable/fast_scoller_bubble.xml create mode 100644 app/src/main/res/drawable/fast_scroller_handle.xml diff --git a/app/src/main/res/drawable/fast_scoller_bubble.xml b/app/src/main/res/drawable/fast_scoller_bubble.xml new file mode 100644 index 000000000..d5b1a6ae6 --- /dev/null +++ b/app/src/main/res/drawable/fast_scoller_bubble.xml @@ -0,0 +1,15 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/fast_scroller_handle.xml b/app/src/main/res/drawable/fast_scroller_handle.xml new file mode 100644 index 000000000..4fc05fe12 --- /dev/null +++ b/app/src/main/res/drawable/fast_scroller_handle.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + From 8a4d648e5ef6d9d0c47907130d6611adabd004ad Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 22 Aug 2024 20:03:25 +0530 Subject: [PATCH 114/188] fix: improvements to biometric prompt for app lock --- .../celzero/bravedns/ui/HomeScreenActivity.kt | 36 +++++-------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/HomeScreenActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/HomeScreenActivity.kt index ac0db1e15..7521fbf99 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/HomeScreenActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/HomeScreenActivity.kt @@ -37,7 +37,6 @@ import androidx.appcompat.app.AppCompatActivity import androidx.biometric.BiometricManager import androidx.biometric.BiometricPrompt import androidx.core.content.ContextCompat -import androidx.core.content.res.ResourcesCompat import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.setupWithNavController @@ -56,7 +55,6 @@ import com.celzero.bravedns.backup.BackupHelper.Companion.BACKUP_FILE_EXTN import com.celzero.bravedns.backup.BackupHelper.Companion.INTENT_RESTART_APP import com.celzero.bravedns.backup.BackupHelper.Companion.INTENT_SCHEME import com.celzero.bravedns.backup.RestoreAgent -import com.celzero.bravedns.data.AppConfig import com.celzero.bravedns.database.AppInfoRepository import com.celzero.bravedns.database.RefreshDatabase import com.celzero.bravedns.databinding.ActivityHomeScreenBinding @@ -100,11 +98,8 @@ class HomeScreenActivity : AppCompatActivity(R.layout.activity_home_screen) { private lateinit var biometricPrompt: BiometricPrompt private lateinit var promptInfo: BiometricPrompt.PromptInfo - // private var biometricPromptRetryCount = 1 - private var onResumeCalledAlready = false - companion object { - private const val ON_RESUME_CALLED_PREFERENCE_KEY = "onResumeCalled" + private const val BIOMETRIC_TIMEOUT_MINUTES = 15L } // TODO - #324 - Usage of isDarkTheme() in all activities. @@ -117,18 +112,19 @@ class HomeScreenActivity : AppCompatActivity(R.layout.activity_home_screen) { setTheme(getCurrentTheme(isDarkThemeOn(), persistentState.theme)) super.onCreate(savedInstanceState) - // stackoverflow.com/questions/44221195/multiple-onstop-onresume-calls-in-android-activity - // Restore value of members from saved state - onResumeCalledAlready = - savedInstanceState?.getBoolean(ON_RESUME_CALLED_PREFERENCE_KEY) ?: false + val isAppRunningOnTv = isAppRunningOnTv() // do not launch on board activity when app is running on TV - if (persistentState.firstTimeLaunch && !isAppRunningOnTv()) { + if (persistentState.firstTimeLaunch && !isAppRunningOnTv) { launchOnboardActivity() return } updateNewVersion() + if (persistentState.biometricAuth && !isAppRunningOnTv) { + biometricPrompt() + } + setupNavigationItemSelectedListener() // handle intent receiver for backup/restore @@ -139,18 +135,6 @@ class HomeScreenActivity : AppCompatActivity(R.layout.activity_home_screen) { observeAppState() } - override fun onSaveInstanceState(outState: Bundle, outPersistentState: PersistableBundle) { - outState.putBoolean(ON_RESUME_CALLED_PREFERENCE_KEY, onResumeCalledAlready) - super.onSaveInstanceState(outState, outPersistentState) - } - - override fun onResume() { - super.onResume() - if (persistentState.biometricAuth && !isAppRunningOnTv() && !onResumeCalledAlready) { - biometricPrompt() - } - } - // check if app running on TV private fun isAppRunningOnTv(): Boolean { return try { @@ -164,10 +148,8 @@ class HomeScreenActivity : AppCompatActivity(R.layout.activity_home_screen) { private fun biometricPrompt() { // if the biometric authentication is already done in the last 15 minutes, then skip // fixme - #324 - move the 15 minutes to a configurable value - if ( - SystemClock.elapsedRealtime() - persistentState.biometricAuthTime < - TimeUnit.MINUTES.toMillis(15) - ) { + val timeSinceLastAuth = SystemClock.elapsedRealtime() - persistentState.biometricAuthTime + if (timeSinceLastAuth < TimeUnit.MINUTES.toMillis(BIOMETRIC_TIMEOUT_MINUTES)) { return } From 75bd2081d32bc0337413599f23cb64eccb1caa55 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 22 Aug 2024 20:06:57 +0530 Subject: [PATCH 115/188] ui: dependency for fast-scroll, bump firestack version --- app/build.gradle | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 373ea0202..9ffa74e8c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -246,11 +246,12 @@ dependencies { fullImplementation 'com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.9' // from: https://jitpack.io/#celzero/firestack - download 'com.github.celzero:firestack:689a5a3d10@aar' - websiteImplementation 'com.github.celzero:firestack:689a5a3d10@aar' - fdroidImplementation 'com.github.celzero:firestack:689a5a3d10@aar' + download 'com.github.celzero:firestack:0751d67f29@aar' + websiteImplementation 'com.github.celzero:firestack:0751d67f29@aar' + fdroidImplementation 'com.github.celzero:firestack:0751d67f29@aar' // debug symbols - playImplementation 'com.github.celzero:firestack:689a5a3d10:debug@aar' + playImplementation 'com.github.celzero:firestack:0751d67f29:debug@aar' + // 0097800c71 // Work manager implementation('androidx.work:work-runtime-ktx:2.9.0') { @@ -288,6 +289,7 @@ dependencies { // barcode scanner for wireguard fullImplementation 'com.journeyapps:zxing-android-embedded:4.3.0' + fullImplementation 'com.simplecityapps:recyclerview-fastscroll:2.0.1' } // github.com/michel-kraemer/gradle-download-task/issues/131#issuecomment-464476903 From 19863161582f94ee713a502c878dde58ca496f98 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 29 Aug 2024 17:14:33 +0530 Subject: [PATCH 116/188] Fix #1665: bigger 'enable' button for WireGuard --- app/src/main/res/layout/list_item_wg_general_interface.xml | 2 +- app/src/main/res/layout/list_item_wg_one_interface.xml | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/list_item_wg_general_interface.xml b/app/src/main/res/layout/list_item_wg_general_interface.xml index a3bae8511..d683e8f83 100644 --- a/app/src/main/res/layout/list_item_wg_general_interface.xml +++ b/app/src/main/res/layout/list_item_wg_general_interface.xml @@ -193,7 +193,7 @@ android:layout_centerVertical="true" android:clickable="true" android:focusable="true" - android:padding="5dp" + android:padding="15dp" android:textColor="@color/colorPrimary" android:textSize="16sp" /> diff --git a/app/src/main/res/layout/list_item_wg_one_interface.xml b/app/src/main/res/layout/list_item_wg_one_interface.xml index 101cc4a9c..85f92f5ce 100644 --- a/app/src/main/res/layout/list_item_wg_one_interface.xml +++ b/app/src/main/res/layout/list_item_wg_one_interface.xml @@ -179,7 +179,9 @@ android:layout_centerVertical="true" android:clickable="true" android:focusable="true" - android:padding="10dp" + android:layoutDirection="rtl" + android:layout_marginEnd="10dp" + android:padding="20dp" android:textColor="@color/colorPrimary" android:textSize="16sp" /> From eeb991f9f07901aeaf5bb3579001de3553f564f8 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 9 Sep 2024 10:36:04 +0530 Subject: [PATCH 117/188] fix: exclude apps in inactive config and return catch-all (if not in lockdown) --- .../bravedns/service/WireguardManager.kt | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt b/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt index b5a8f0c37..5550978db 100644 --- a/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt +++ b/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt @@ -150,6 +150,10 @@ object WireguardManager : KoinComponent { return mappings.any { it.isActive } } + fun isAdvancedWgActive(): Boolean { + return mappings.any { it.isActive && !it.oneWireGuard } + } + fun getEnabledConfigs(): List { val m = mappings.filter { it.isActive } val l = mutableListOf() @@ -461,32 +465,22 @@ object WireguardManager : KoinComponent { } } // check if any catch-all config is enabled - if (configId == "" || !configId.contains(ProxyManager.ID_WG_BASE) || config == null) { - Logger.d(LOG_TAG_PROXY, "app config mapping not found for uid: $uid") - // there maybe catch-all config enabled, so return the active catch-all config - val catchAllConfig = mappings.find { it.isActive && it.isCatchAll } - return if (catchAllConfig == null) { - Logger.d(LOG_TAG_PROXY, "catch all config not found for uid: $uid") + Logger.d(LOG_TAG_PROXY, "app config mapping not found for uid: $uid") + // there maybe catch-all config enabled, so return the active catch-all config + val catchAllConfig = mappings.find { it.isActive && it.isCatchAll } + return if (catchAllConfig == null) { + Logger.d(LOG_TAG_PROXY, "catch all config not found for uid: $uid") + null + } else { + val optimalId = fetchOptimalCatchAllConfig(uid, ip) + if (optimalId == null) { + Logger.d(LOG_TAG_PROXY, "no catch all config found for uid: $uid") null } else { - val optimalId = fetchOptimalCatchAllConfig(uid, ip) - if (optimalId == null) { - Logger.d(LOG_TAG_PROXY, "no catch all config found for uid: $uid") - null - } else { - Logger.i(LOG_TAG_PROXY, "catch all config found for uid: $uid, $optimalId") - mappings.find { it.id == optimalId } - } + Logger.i(LOG_TAG_PROXY, "catch all config found for uid: $uid, $optimalId") + mappings.find { it.id == optimalId } } } - - // no catch-all wg matches the uid & ip combination, return config (assigned config) - if (config.isActive || config.isLockdown) { - Logger.d(LOG_TAG_PROXY, "app config mapping found for uid: $uid, $configId") - return config - } - - return null } private fun convertStringIdToId(id: String): Int { From 6f5ea0b2b1c398099357ec9dfedac6a078ff7e7e Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 9 Sep 2024 10:51:46 +0530 Subject: [PATCH 118/188] fix: change logger stmt for catch-all config selection --- .../java/com/celzero/bravedns/service/WireguardManager.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt b/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt index 5550978db..64251e21b 100644 --- a/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt +++ b/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt @@ -464,20 +464,21 @@ object WireguardManager : KoinComponent { // pass-through and check if any catch-all config is enabled } } + // check if any catch-all config is enabled Logger.d(LOG_TAG_PROXY, "app config mapping not found for uid: $uid") - // there maybe catch-all config enabled, so return the active catch-all config val catchAllConfig = mappings.find { it.isActive && it.isCatchAll } return if (catchAllConfig == null) { Logger.d(LOG_TAG_PROXY, "catch all config not found for uid: $uid") null } else { + // if catch-all config is enabled, check for the validity of the connection val optimalId = fetchOptimalCatchAllConfig(uid, ip) if (optimalId == null) { Logger.d(LOG_TAG_PROXY, "no catch all config found for uid: $uid") null } else { - Logger.i(LOG_TAG_PROXY, "catch all config found for uid: $uid, $optimalId") + Logger.d(LOG_TAG_PROXY, "catch all config found for uid: $uid, $optimalId") mappings.find { it.id == optimalId } } } From 7b198268006eb3f6a675b8400da6738b85f21c82 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 9 Sep 2024 10:54:14 +0530 Subject: [PATCH 119/188] icons: new icons for settings introduced in v055o --- .../res/drawable/ic_advanced_settings.xml | 9 ++++ app/src/main/res/drawable/ic_android_icon.xml | 21 ++++++++ app/src/main/res/drawable/ic_anti_dpi.xml | 22 +++++++++ .../res/drawable/ic_endpoint_independent.xml | 22 +++++++++ .../res/drawable/ic_randomize_wg_port.xml | 10 ++++ app/src/main/res/drawable/report_bug_icon.xml | 48 ------------------- 6 files changed, 84 insertions(+), 48 deletions(-) create mode 100644 app/src/main/res/drawable/ic_advanced_settings.xml create mode 100644 app/src/main/res/drawable/ic_android_icon.xml create mode 100644 app/src/main/res/drawable/ic_anti_dpi.xml create mode 100644 app/src/main/res/drawable/ic_endpoint_independent.xml create mode 100644 app/src/main/res/drawable/ic_randomize_wg_port.xml delete mode 100644 app/src/main/res/drawable/report_bug_icon.xml diff --git a/app/src/main/res/drawable/ic_advanced_settings.xml b/app/src/main/res/drawable/ic_advanced_settings.xml new file mode 100644 index 000000000..42fe30f79 --- /dev/null +++ b/app/src/main/res/drawable/ic_advanced_settings.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_android_icon.xml b/app/src/main/res/drawable/ic_android_icon.xml new file mode 100644 index 000000000..e5de7a52b --- /dev/null +++ b/app/src/main/res/drawable/ic_android_icon.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_anti_dpi.xml b/app/src/main/res/drawable/ic_anti_dpi.xml new file mode 100644 index 000000000..7d7c19955 --- /dev/null +++ b/app/src/main/res/drawable/ic_anti_dpi.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_endpoint_independent.xml b/app/src/main/res/drawable/ic_endpoint_independent.xml new file mode 100644 index 000000000..a83cedec4 --- /dev/null +++ b/app/src/main/res/drawable/ic_endpoint_independent.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_randomize_wg_port.xml b/app/src/main/res/drawable/ic_randomize_wg_port.xml new file mode 100644 index 000000000..2af24bd91 --- /dev/null +++ b/app/src/main/res/drawable/ic_randomize_wg_port.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/report_bug_icon.xml b/app/src/main/res/drawable/report_bug_icon.xml deleted file mode 100644 index 8232f9242..000000000 --- a/app/src/main/res/drawable/report_bug_icon.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - From cd7f97fdd3a95449efa09d944d308b992f557838 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 9 Sep 2024 11:01:32 +0530 Subject: [PATCH 120/188] icon: fix bug report icon --- app/src/main/res/layout/activity_network_logs.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/activity_network_logs.xml b/app/src/main/res/layout/activity_network_logs.xml index ce0041b6d..b4dc01b36 100644 --- a/app/src/main/res/layout/activity_network_logs.xml +++ b/app/src/main/res/layout/activity_network_logs.xml @@ -29,7 +29,7 @@ android:layout_centerVertical="true" android:alpha="0.7" android:layout_alignParentEnd="true" - android:src="@drawable/report_bug_icon" /> + android:src="@drawable/ic_android_icon" /> From 7b977ef207caeb179a2e0f93d2a4d7c076174e29 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 9 Sep 2024 11:04:15 +0530 Subject: [PATCH 121/188] icon: fix bug report icon --- app/src/main/res/layout/fragment_about.xml | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/layout/fragment_about.xml b/app/src/main/res/layout/fragment_about.xml index 72b9eae23..9a9565f88 100644 --- a/app/src/main/res/layout/fragment_about.xml +++ b/app/src/main/res/layout/fragment_about.xml @@ -86,6 +86,19 @@ android:text="@string/about_bravedns_whoarewe" android:textSize="@dimen/large_font_text_view" /> + + @@ -212,7 +225,7 @@ android:layout_height="wrap_content" android:background="@drawable/rectangle_border_background" android:clickable="true" - android:drawableStart="@drawable/report_bug_icon" + android:drawableStart="@drawable/ic_android_icon" android:drawablePadding="-15dp" android:focusable="true" android:foreground="?attr/selectableItemBackground" From df5cfd861cb12830a27ad2a98218610a0316f61a Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 9 Sep 2024 15:37:25 +0530 Subject: [PATCH 122/188] update target IPs if available in the summary --- .../bravedns/database/ConnectionTrackerRepository.kt | 6 +----- .../java/com/celzero/bravedns/service/NetLogTracker.kt | 8 ++++---- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerRepository.kt b/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerRepository.kt index b4617a253..f4700a8e5 100644 --- a/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerRepository.kt +++ b/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerRepository.kt @@ -16,7 +16,6 @@ package com.celzero.bravedns.database import androidx.lifecycle.LiveData -import com.celzero.bravedns.RethinkDnsApplication import com.celzero.bravedns.RethinkDnsApplication.Companion.DEBUG import com.celzero.bravedns.data.ConnectionSummary import com.celzero.bravedns.data.DataUsage @@ -37,12 +36,9 @@ class ConnectionTrackerRepository(private val connectionTrackerDAO: ConnectionTr } suspend fun updateBatch(summary: List) { - val d = Logger.LoggerType.fromId(persistentState.goLoggerLevel.toInt()) - val debug = d.isLessThan(Logger.LoggerType.INFO) // debug, verbose, very verbose - summary.forEach { // update the flag and target ip if in debug mode - if (debug && !it.targetIp.isNullOrEmpty()) { + if (!it.targetIp.isNullOrEmpty()) { val flag = it.flag ?: "" connectionTrackerDAO.updateSummary( it.connId, diff --git a/app/src/main/java/com/celzero/bravedns/service/NetLogTracker.kt b/app/src/main/java/com/celzero/bravedns/service/NetLogTracker.kt index 86eecabc1..7cf56b263 100644 --- a/app/src/main/java/com/celzero/bravedns/service/NetLogTracker.kt +++ b/app/src/main/java/com/celzero/bravedns/service/NetLogTracker.kt @@ -19,7 +19,9 @@ package com.celzero.bravedns.service import Logger.LOG_BATCH_LOGGER import android.content.Context import android.util.Log +import backend.Backend import backend.DNSSummary +import com.celzero.bravedns.RethinkDnsApplication import com.celzero.bravedns.RethinkDnsApplication.Companion.DEBUG import com.celzero.bravedns.data.ConnTrackerMetaData import com.celzero.bravedns.data.ConnectionSummary @@ -161,12 +163,10 @@ internal constructor( fun updateIpSummary(summary: ConnectionSummary) { if (!persistentState.logsEnabled) return - val d = Logger.LoggerType.fromId(persistentState.goLoggerLevel.toInt()) - val debug = d.isLessThan(Logger.LoggerType.INFO) // debug, verbose, very verbose serializer("updateIpSmm", looper) { val s = - if (debug && summary.targetIp?.isNotEmpty() == true) { + if (summary.targetIp?.isNotEmpty() == true) { ipdb.makeSummaryWithTarget(summary) } else { summary @@ -184,7 +184,7 @@ internal constructor( serializer("updateRethinkSmm", looper) { val s = - if (DEBUG && summary.targetIp?.isNotEmpty() == true) { + if (summary.targetIp?.isNotEmpty() == true) { ipdb.makeSummaryWithTarget(summary) } else { summary From d715e76a824a124b14c0507626361f5d9f124104 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 9 Sep 2024 20:45:27 +0530 Subject: [PATCH 123/188] show known ports name only for UDP and TCP conns --- .../celzero/bravedns/adapter/ConnectionTrackerAdapter.kt | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/ConnectionTrackerAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/ConnectionTrackerAdapter.kt index 517ef94ff..ad7b40c33 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/ConnectionTrackerAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/ConnectionTrackerAdapter.kt @@ -185,8 +185,13 @@ class ConnectionTrackerAdapter(private val context: Context) : } private fun displayProtocolDetails(port: Int, proto: Int) { - // Instead of showing the port name and protocol, now the ports are resolved with - // known ports(reserved port and protocol identifiers). + // If the protocol is not TCP or UDP, then display the protocol name. + if (Protocol.UDP.protocolType != proto && Protocol.TCP.protocolType != proto) { + b.connLatencyTxt.text = Protocol.getProtocolName(proto).name + return + } + + // Instead of displaying the port number, display the service name if it is known. // https://github.com/celzero/rethink-app/issues/42 - #3 - transport + protocol. val resolvedPort = KnownPorts.resolvePort(port) // case: for UDP/443 label it as HTTP3 instead of HTTPS From 8b4921529725de30ccd7098eab09b3b31a76f5c6 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Mon, 9 Sep 2024 20:53:56 +0530 Subject: [PATCH 124/188] proxy-app map: set alpha based on internet permission --- .../bravedns/adapter/FirewallAppListAdapter.kt | 8 +++++--- .../celzero/bravedns/adapter/WgIncludeAppsAdapter.kt | 10 ++++++++++ .../bravedns/database/ProxyApplicationMapping.kt | 12 ++++++++++++ 3 files changed, 27 insertions(+), 3 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt index 8296c6918..8f8edcc76 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt @@ -112,15 +112,17 @@ class FirewallAppListAdapter( } else { b.firewallAppLabelTv.setTextColor(userAppColor) } */ + b.firewallAppToggleOther.text = getFirewallText(appStatus, connStatus) + displayIcon( + getIcon(context, appInfo.packageName, appInfo.appName), b.firewallAppIconIv) // set the alpha based on internet permission if (appInfo.hasInternetPermission(packageManager)) { b.firewallAppLabelTv.alpha = 1f + b.firewallAppIconIv.alpha = 1f } else { b.firewallAppLabelTv.alpha = 0.4f + b.firewallAppIconIv.alpha = 0.4f } - b.firewallAppToggleOther.text = getFirewallText(appStatus, connStatus) - displayIcon( - getIcon(context, appInfo.packageName, appInfo.appName), b.firewallAppIconIv) if (appInfo.packageName == context.packageName) { b.firewallAppToggleWifi.visibility = View.GONE b.firewallAppToggleMobileData.visibility = View.GONE diff --git a/app/src/full/java/com/celzero/bravedns/adapter/WgIncludeAppsAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/WgIncludeAppsAdapter.kt index ee8be984a..85bfb5ccb 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/WgIncludeAppsAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/WgIncludeAppsAdapter.kt @@ -19,6 +19,7 @@ import Logger import Logger.LOG_TAG_PROXY import android.content.Context import android.content.DialogInterface +import android.content.pm.PackageManager import android.graphics.drawable.Drawable import android.view.LayoutInflater import android.view.View @@ -54,6 +55,7 @@ class WgIncludeAppsAdapter( PagingDataAdapter( DIFF_CALLBACK ) { + private val packageManager: PackageManager = context.packageManager companion object { @@ -138,6 +140,14 @@ class WgIncludeAppsAdapter( val isIncluded = mapping.proxyId == proxyId && mapping.proxyId != "" ui { displayIcon(getIcon(context, mapping.packageName, mapping.appName)) } + // set the alpha based on internet permission + if (mapping.hasInternetPermission(packageManager)) { + b.wgIncludeAppListApkLabelTv.alpha = 1f + b.wgIncludeAppListApkIconIv.alpha = 1f + } else { + b.wgIncludeAppListApkLabelTv.alpha = 0.4f + b.wgIncludeAppListApkIconIv.alpha = 0.4f + } setupClickListeners(mapping, isIncluded) } diff --git a/app/src/main/java/com/celzero/bravedns/database/ProxyApplicationMapping.kt b/app/src/main/java/com/celzero/bravedns/database/ProxyApplicationMapping.kt index 1751d7466..e237c35e3 100644 --- a/app/src/main/java/com/celzero/bravedns/database/ProxyApplicationMapping.kt +++ b/app/src/main/java/com/celzero/bravedns/database/ProxyApplicationMapping.kt @@ -15,6 +15,8 @@ */ package com.celzero.bravedns.database +import android.Manifest +import android.content.pm.PackageManager import androidx.room.Entity @Entity(tableName = "ProxyApplicationMapping", primaryKeys = ["uid", "packageName", "proxyId"]) @@ -55,4 +57,14 @@ class ProxyApplicationMapping { this.isActive = isActive this.proxyId = proxyId } + + fun hasInternetPermission(packageManager: PackageManager): Boolean { + if (packageName.startsWith("no_package_")) return true + + // INTERNET permission if defined, can not be denied so this is safe to use + return packageManager.checkPermission( + Manifest.permission.INTERNET, + packageName + ) == PackageManager.PERMISSION_GRANTED + } } From 8f0c6288f488cec871d3a2bb7a85c3e0f9ee46b7 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Tue, 10 Sep 2024 17:48:40 +0530 Subject: [PATCH 125/188] filter logs for specific wireguard configuration in network log screen --- .../ui/activity/WgConfigDetailActivity.kt | 16 +++++++++++++ .../main/res/layout/activity_wg_detail.xml | 23 +++++++++++++++++-- 2 files changed, 37 insertions(+), 2 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt index 7d9013d20..7c9608897 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt @@ -47,6 +47,7 @@ import com.celzero.bravedns.service.WireguardManager.ERR_CODE_WG_INVALID import com.celzero.bravedns.service.WireguardManager.INVALID_CONF_ID import com.celzero.bravedns.ui.dialog.WgAddPeerDialog import com.celzero.bravedns.ui.dialog.WgIncludeAppsDialog +import com.celzero.bravedns.util.Constants import com.celzero.bravedns.util.Themes import com.celzero.bravedns.util.UIUtils import com.celzero.bravedns.util.Utilities @@ -448,6 +449,21 @@ class WgConfigDetailActivity : AppCompatActivity(R.layout.activity_wg_detail) { b.lockdownCheck.setOnClickListener { updateLockdown(b.lockdownCheck.isChecked) } b.catchAllCheck.setOnClickListener { updateCatchAll(b.catchAllCheck.isChecked) } + + b.logsBtn.setOnClickListener { + startActivity( + NetworkLogsActivity.Tabs.NETWORK_LOGS.screen, + ProxyManager.ID_WG_BASE + configId + ) + } + } + + private fun startActivity(screenToLoad: Int, searchParam: String?) { + val intent = Intent(this, NetworkLogsActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + intent.putExtra(Constants.VIEW_PAGER_SCREEN_TO_LOAD, screenToLoad) + intent.putExtra(Constants.SEARCH_QUERY, searchParam ?: "") + startActivity(intent) } private fun updateLockdown(enabled: Boolean) { diff --git a/app/src/main/res/layout/activity_wg_detail.xml b/app/src/main/res/layout/activity_wg_detail.xml index c13910d0e..715e2d71b 100644 --- a/app/src/main/res/layout/activity_wg_detail.xml +++ b/app/src/main/res/layout/activity_wg_detail.xml @@ -263,8 +263,8 @@ android:layout_marginEnd="5dp" android:layout_weight="0.5" android:background="@drawable/home_screen_button_stop_bg" - android:foreground="?attr/selectableItemBackground" android:fontFamily="sans-serif-smallcaps" + android:foreground="?attr/selectableItemBackground" android:gravity="center" android:padding="10dp" android:text="@string/rt_edit_dialog_positive" @@ -279,8 +279,8 @@ android:layout_marginStart="5dp" android:layout_weight="0.5" android:background="@drawable/home_screen_button_stop_bg" - android:foreground="?attr/selectableItemBackground" android:fontFamily="sans-serif-smallcaps" + android:foreground="?attr/selectableItemBackground" android:gravity="center" android:padding="10dp" android:text="@string/ci_delete" @@ -440,6 +440,25 @@ android:textSize="@dimen/large_font_text_view" app:rippleColor="?attr/secondaryTextColor" /> + +
From 68095f92f6a4d666ff2b220141d6b36360e489a8 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Wed, 18 Sep 2024 19:50:27 +0530 Subject: [PATCH 126/188] ui: set selectable bg for btns in edit ip dialog --- app/src/main/res/layout/dialog_input_ips.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/layout/dialog_input_ips.xml b/app/src/main/res/layout/dialog_input_ips.xml index 51d5bd6fb..4cc518898 100644 --- a/app/src/main/res/layout/dialog_input_ips.xml +++ b/app/src/main/res/layout/dialog_input_ips.xml @@ -307,6 +307,7 @@ android:background="@drawable/rectangle_border_background" android:layout_width="0dp" android:gravity="center" + android:foreground="?android:attr/selectableItemBackground" android:padding="10dp" android:layout_height="wrap_content" android:layout_weight="0.5" @@ -319,6 +320,7 @@ android:background="@drawable/rectangle_border_background" android:layout_width="0dp" android:layout_height="wrap_content" + android:foreground="?android:attr/selectableItemBackground" android:gravity="center" android:padding="10dp" android:textColor="?attr/accentGood" From 17c1c36b8deb4db0dbb5c8c7f0d9d8e8d26385ed Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 19 Sep 2024 16:36:15 +0530 Subject: [PATCH 127/188] ui: new screen to show consolidated apps details from stats screen --- .../adapter/DomainConnectionsAdapter.kt | 150 ++++++++++++++++++ .../adapter/SummaryStatisticsAdapter.kt | 35 ++-- .../ui/activity/DomainConnectionsActivity.kt | 145 +++++++++++++++++ .../viewmodel/DomainConnectionsViewModel.kt | 97 +++++++++++ .../bravedns/viewmodel/ViewModelModule.kt | 1 + .../layout/activity_domain_connections.xml | 107 +++++++++++++ 6 files changed, 523 insertions(+), 12 deletions(-) create mode 100644 app/src/full/java/com/celzero/bravedns/adapter/DomainConnectionsAdapter.kt create mode 100644 app/src/full/java/com/celzero/bravedns/ui/activity/DomainConnectionsActivity.kt create mode 100644 app/src/full/java/com/celzero/bravedns/viewmodel/DomainConnectionsViewModel.kt create mode 100644 app/src/main/res/layout/activity_domain_connections.xml diff --git a/app/src/full/java/com/celzero/bravedns/adapter/DomainConnectionsAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/DomainConnectionsAdapter.kt new file mode 100644 index 000000000..c8aaf011d --- /dev/null +++ b/app/src/full/java/com/celzero/bravedns/adapter/DomainConnectionsAdapter.kt @@ -0,0 +1,150 @@ +/* + * Copyright 2024 RethinkDNS and its authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.celzero.bravedns.adapter + +import android.content.Context +import android.content.Intent +import android.graphics.drawable.Drawable +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.LifecycleOwner +import androidx.lifecycle.lifecycleScope +import androidx.paging.PagingDataAdapter +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.celzero.bravedns.R +import com.celzero.bravedns.data.AppConnection +import com.celzero.bravedns.databinding.ListItemStatisticsSummaryBinding +import com.celzero.bravedns.service.FirewallManager +import com.celzero.bravedns.ui.activity.AppInfoActivity +import com.celzero.bravedns.util.Utilities +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class DomainConnectionsAdapter(private val context: Context) : + PagingDataAdapter( + DIFF_CALLBACK + ) { + + companion object { + private val DIFF_CALLBACK = + object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldConnection: AppConnection, + newConnection: AppConnection + ): Boolean { + return (oldConnection == newConnection) + } + + override fun areContentsTheSame( + oldConnection: AppConnection, + newConnection: AppConnection + ): Boolean { + return (oldConnection == newConnection) + } + } + } + + override fun onCreateViewHolder( + parent: ViewGroup, + viewType: Int + ): DomainConnectionsViewHolder { + val itemBinding = + ListItemStatisticsSummaryBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + return DomainConnectionsViewHolder(itemBinding) + } + + override fun onBindViewHolder(holder: DomainConnectionsViewHolder, position: Int) { + val appNetworkActivity = getItem(position) ?: return + holder.bind(appNetworkActivity) + } + + inner class DomainConnectionsViewHolder(private val b: ListItemStatisticsSummaryBinding) : + RecyclerView.ViewHolder(b.root) { + + fun bind(dc: AppConnection) { + b.ssDataUsage.text = dc.appOrDnsName + io { + val appInfo = FirewallManager.getAppInfoByUid(dc.uid) + uiCtx { + b.ssIcon.visibility = View.VISIBLE + b.ssFlag.visibility = View.GONE + loadAppIcon( + Utilities.getIcon( + context, + appInfo?.packageName ?: "", + appInfo?.appName ?: "" + ) + ) + } + } + if (dc.downloadBytes == null || dc.uploadBytes == null) { + return + } + + val download = + context.getString( + R.string.symbol_download, + Utilities.humanReadableByteCount(dc.downloadBytes, true) + ) + val upload = + context.getString( + R.string.symbol_upload, + Utilities.humanReadableByteCount(dc.uploadBytes, true) + ) + val total = context.getString(R.string.two_argument, upload, download) + b.ssName.text = total + b.ssCount.text = dc.count.toString() + + b.ssProgress.visibility = View.GONE + + b.ssContainer.setOnClickListener { + val intent = Intent(context, AppInfoActivity::class.java) + intent.putExtra(AppInfoActivity.INTENT_UID, dc.uid) + context.startActivity(intent) + } + + } + + private fun loadAppIcon(drawable: Drawable?) { + ui { + Glide.with(context) + .load(drawable) + .error(Utilities.getDefaultIcon(context)) + .into(b.ssIcon) + } + } + } + + private fun io(f: suspend () -> Unit) { + (context as LifecycleOwner).lifecycleScope.launch(Dispatchers.IO) { f() } + } + + private fun ui(f: suspend () -> Unit) { + (context as LifecycleOwner).lifecycleScope.launch(Dispatchers.Main) { f() } + } + + private suspend fun uiCtx(f: suspend () -> Unit) { + withContext(Dispatchers.Main) { f() } + } +} \ No newline at end of file diff --git a/app/src/full/java/com/celzero/bravedns/adapter/SummaryStatisticsAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/SummaryStatisticsAdapter.kt index 610d0edbb..f4ed4ccb9 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/SummaryStatisticsAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/SummaryStatisticsAdapter.kt @@ -17,6 +17,7 @@ package com.celzero.bravedns.adapter import Logger import Logger.LOG_TAG_DNS +import Logger.LOG_TAG_UI import android.content.Context import android.content.Intent import android.graphics.drawable.Drawable @@ -45,6 +46,7 @@ import com.celzero.bravedns.glide.FavIconDownloader import com.celzero.bravedns.service.FirewallManager import com.celzero.bravedns.service.PersistentState import com.celzero.bravedns.ui.activity.AppInfoActivity +import com.celzero.bravedns.ui.activity.DomainConnectionsActivity import com.celzero.bravedns.ui.activity.NetworkLogsActivity import com.celzero.bravedns.ui.fragment.SummaryStatisticsFragment.SummaryStatisticsType import com.celzero.bravedns.util.Constants @@ -52,6 +54,7 @@ import com.celzero.bravedns.util.UIUtils.fetchToggleBtnColors import com.celzero.bravedns.util.UIUtils.getCountryNameFromFlag import com.celzero.bravedns.util.Utilities import com.celzero.bravedns.util.Utilities.isAtleastN +import com.celzero.bravedns.viewmodel.SummaryStatisticsViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -68,6 +71,7 @@ class SummaryStatisticsAdapter( ) { private var maxValue: Int = 0 + private var timeCategory = SummaryStatisticsViewModel.TimeCategory.ONE_HOUR companion object { private val DIFF_CALLBACK = @@ -86,6 +90,7 @@ class SummaryStatisticsAdapter( return (oldConnection == newConnection) } } + } override fun onCreateViewHolder( @@ -119,6 +124,10 @@ class SummaryStatisticsAdapter( } } + fun setTimeCategory(timeCategory: SummaryStatisticsViewModel.TimeCategory) { + this.timeCategory = timeCategory + } + inner class AppNetworkActivityViewHolder( private val itemBinding: ListItemStatisticsSummaryBinding ) : RecyclerView.ViewHolder(itemBinding.root) { @@ -373,14 +382,7 @@ class SummaryStatisticsAdapter( startAppInfoActivity(appConnection) } SummaryStatisticsType.MOST_CONTACTED_DOMAINS -> { - if (appConfig.getBraveMode().isDnsMode()) { - showDnsLogs(appConnection) - } else { - showNetworkLogs( - appConnection, - SummaryStatisticsType.MOST_CONTACTED_DOMAINS - ) - } + startDomainConnectionsActivity(appConnection, DomainConnectionsActivity.InputType.DOMAIN) } SummaryStatisticsType.MOST_BLOCKED_DOMAINS -> { io { @@ -409,10 +411,7 @@ class SummaryStatisticsAdapter( showNetworkLogs(appConnection, SummaryStatisticsType.MOST_BLOCKED_IPS) } SummaryStatisticsType.MOST_CONTACTED_COUNTRIES -> { - showNetworkLogs( - appConnection, - SummaryStatisticsType.MOST_CONTACTED_COUNTRIES - ) + startDomainConnectionsActivity(appConnection, DomainConnectionsActivity.InputType.FLAG) } SummaryStatisticsType.MOST_BLOCKED_COUNTRIES -> { showNetworkLogs(appConnection, SummaryStatisticsType.MOST_BLOCKED_COUNTRIES) @@ -421,6 +420,18 @@ class SummaryStatisticsAdapter( } } + private fun startDomainConnectionsActivity(appConnection: AppConnection, input: DomainConnectionsActivity.InputType) { + val intent = Intent(context, DomainConnectionsActivity::class.java) + intent.putExtra(DomainConnectionsActivity.INTENT_TYPE, input.type) + if (input == DomainConnectionsActivity.InputType.DOMAIN) { + intent.putExtra(DomainConnectionsActivity.INTENT_DOMAIN, appConnection.appOrDnsName) + } else { + intent.putExtra(DomainConnectionsActivity.INTENT_FLAG, appConnection.flag) + } + intent.putExtra(DomainConnectionsActivity.INTENT_TIME_CATEGORY, timeCategory.value) + context.startActivity(intent) + } + private fun startAppInfoActivity(appConnection: AppConnection) { val intent = Intent(context, AppInfoActivity::class.java) intent.putExtra(AppInfoActivity.INTENT_UID, appConnection.uid) diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/DomainConnectionsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/DomainConnectionsActivity.kt new file mode 100644 index 000000000..a341de751 --- /dev/null +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/DomainConnectionsActivity.kt @@ -0,0 +1,145 @@ +/* + * Copyright 2024 RethinkDNS and its authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.celzero.bravedns.ui.activity + +import Logger.LOG_TAG_UI +import android.content.Context +import android.content.res.Configuration +import android.content.res.Configuration.UI_MODE_NIGHT_YES +import android.os.Bundle +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import by.kirich1409.viewbindingdelegate.viewBinding +import com.celzero.bravedns.R +import com.celzero.bravedns.adapter.DomainConnectionsAdapter +import com.celzero.bravedns.databinding.ActivityDomainConnectionsBinding +import com.celzero.bravedns.service.PersistentState +import com.celzero.bravedns.util.CustomLinearLayoutManager +import com.celzero.bravedns.util.Themes.Companion.getCurrentTheme +import com.celzero.bravedns.viewmodel.DomainConnectionsViewModel +import com.celzero.bravedns.viewmodel.SummaryStatisticsViewModel +import org.koin.android.ext.android.inject +import org.koin.androidx.viewmodel.ext.android.viewModel + +class DomainConnectionsActivity : AppCompatActivity(R.layout.activity_domain_connections){ + private val b by viewBinding(ActivityDomainConnectionsBinding::bind) + private val persistentState by inject() + private val viewModel by viewModel() + + private var type: InputType = InputType.DOMAIN + + private fun Context.isDarkThemeOn(): Boolean { + return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == + UI_MODE_NIGHT_YES + } + + companion object { + const val INTENT_TYPE = "TYPE" + const val INTENT_FLAG = "FLAG" + const val INTENT_DOMAIN = "DOMAIN" + const val INTENT_TIME_CATEGORY = "TIME_CATEGORY" + } + + enum class InputType(val type: Int) { + DOMAIN(0), FLAG(1) + } + + override fun onCreate(savedInstanceState: Bundle?) { + setTheme(getCurrentTheme(isDarkThemeOn(), persistentState.theme)) + super.onCreate(savedInstanceState) + val t = intent.getIntExtra(INTENT_TYPE, 0) + type = InputType.entries.toTypedArray()[t] + if (type == InputType.DOMAIN) { + val domain = intent.getStringExtra(INTENT_DOMAIN) ?: "" + viewModel.setDomain(domain) + b.dcTitle.visibility = View.VISIBLE + b.dcTitle.text = domain + b.dcFlag.visibility = View.GONE + } else { + val flag = intent.getStringExtra(INTENT_FLAG) ?: "" + viewModel.setFlag(flag) + b.dcFlag.visibility = View.VISIBLE + b.dcFlag.text = flag + b.dcTitle.visibility = View.GONE + Logger.d(LOG_TAG_UI, "Flag: $flag, Type: $type") + } + val tc = intent.getIntExtra(INTENT_TIME_CATEGORY, 0) + val timeCategory = + DomainConnectionsViewModel.TimeCategory.fromValue(tc) + ?: DomainConnectionsViewModel.TimeCategory.ONE_HOUR + setSubTitle(timeCategory) + + viewModel.timeCategoryChanged(timeCategory) + setRecyclerView() + } + + private fun setSubTitle(timeCategory: DomainConnectionsViewModel.TimeCategory) { + b.dcSubtitle.text = + when (timeCategory) { + DomainConnectionsViewModel.TimeCategory.ONE_HOUR -> { + getString( + R.string.three_argument, + getString(R.string.lbl_last), + getString(R.string.numeric_one), + getString(R.string.lbl_hour) + ) + } + + DomainConnectionsViewModel.TimeCategory.TWENTY_FOUR_HOUR -> { + getString( + R.string.three_argument, + getString(R.string.lbl_last), + getString(R.string.numeric_twenty_four), + getString(R.string.lbl_hour) + ) + } + + DomainConnectionsViewModel.TimeCategory.SEVEN_DAYS -> { + getString( + R.string.three_argument, + getString(R.string.lbl_last), + getString(R.string.numeric_seven), + getString(R.string.lbl_day) + ) + } + } + } + + private fun setRecyclerView() { + b.dcRecycler.setHasFixedSize(true) + val layoutManager = CustomLinearLayoutManager(this) + b.dcRecycler.layoutManager = layoutManager + + val recyclerAdapter = DomainConnectionsAdapter(this) + + if (type == InputType.DOMAIN) { + viewModel.domainConnectionList.observe(this) { recyclerAdapter.submitData(this.lifecycle, it) } + } else { + viewModel.flagConnectionList.observe(this) { recyclerAdapter.submitData(this.lifecycle, it) } + } + + // remove the view if there is no data + recyclerAdapter.addLoadStateListener { + if (it.append.endOfPaginationReached) { + if (recyclerAdapter.itemCount < 1) { + b.dcRecycler.visibility = View.GONE + b.dcRecycler.visibility = View.VISIBLE + } + } + } + b.dcRecycler.adapter = recyclerAdapter + } +} \ No newline at end of file diff --git a/app/src/full/java/com/celzero/bravedns/viewmodel/DomainConnectionsViewModel.kt b/app/src/full/java/com/celzero/bravedns/viewmodel/DomainConnectionsViewModel.kt new file mode 100644 index 000000000..cae2807ca --- /dev/null +++ b/app/src/full/java/com/celzero/bravedns/viewmodel/DomainConnectionsViewModel.kt @@ -0,0 +1,97 @@ +/* + * Copyright 2024 RethinkDNS and its authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.celzero.bravedns.viewmodel + +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.switchMap +import androidx.lifecycle.viewModelScope +import androidx.paging.Pager +import androidx.paging.PagingConfig +import androidx.paging.cachedIn +import androidx.paging.liveData +import com.celzero.bravedns.database.ConnectionTrackerDAO +import com.celzero.bravedns.util.Constants + +class DomainConnectionsViewModel(private val connectionTrackerDAO: ConnectionTrackerDAO) : ViewModel() { + private var domains: MutableLiveData = MutableLiveData() + private var flag: MutableLiveData = MutableLiveData() + private var timeCategory: TimeCategory = TimeCategory.ONE_HOUR + private var startTime: MutableLiveData = MutableLiveData() + + companion object { + private const val ONE_HOUR_MILLIS = 1 * 60 * 60 * 1000L + private const val ONE_DAY_MILLIS = 24 * ONE_HOUR_MILLIS + private const val ONE_WEEK_MILLIS = 7 * ONE_DAY_MILLIS + } + + enum class TimeCategory(val value: Int) { + ONE_HOUR(0), + TWENTY_FOUR_HOUR(1), + SEVEN_DAYS(2); + + companion object { + fun fromValue(value: Int) = entries.firstOrNull { it.value == value } + } + } + + init { + // set from and to time to current and 1 hr before + startTime.value = System.currentTimeMillis() - ONE_HOUR_MILLIS + domains.postValue("") + } + + fun setDomain(domain: String) { + domains.postValue(domain) + } + + fun setFlag(flag: String) { + this.flag.postValue(flag) + } + + fun timeCategoryChanged(tc: TimeCategory) { + timeCategory = tc + when (tc) { + TimeCategory.ONE_HOUR -> { + startTime.value = System.currentTimeMillis() - ONE_HOUR_MILLIS + } + TimeCategory.TWENTY_FOUR_HOUR -> { + startTime.value = System.currentTimeMillis() - ONE_DAY_MILLIS + } + TimeCategory.SEVEN_DAYS -> { + startTime.value = System.currentTimeMillis() - ONE_WEEK_MILLIS + } + } + } + + val domainConnectionList = domains.switchMap { input -> + fetchDomainConnections(input) + } + + val flagConnectionList = flag.switchMap { input -> + fetchFlagConnections(input) + } + + private fun fetchDomainConnections(input: String) = + Pager(PagingConfig(pageSize = Constants.LIVEDATA_PAGE_SIZE)) { + connectionTrackerDAO.getDomainConnections(input, startTime.value!!) + }.liveData.cachedIn(viewModelScope) + + private fun fetchFlagConnections(input: String) = + Pager(PagingConfig(pageSize = Constants.LIVEDATA_PAGE_SIZE)) { + connectionTrackerDAO.getFlagConnections(input, startTime.value!!) + }.liveData.cachedIn(viewModelScope) +} diff --git a/app/src/full/java/com/celzero/bravedns/viewmodel/ViewModelModule.kt b/app/src/full/java/com/celzero/bravedns/viewmodel/ViewModelModule.kt index c4e1b5595..867acd7ae 100644 --- a/app/src/full/java/com/celzero/bravedns/viewmodel/ViewModelModule.kt +++ b/app/src/full/java/com/celzero/bravedns/viewmodel/ViewModelModule.kt @@ -45,6 +45,7 @@ object ViewModelModule { viewModel { RethinkLogViewModel(get()) } viewModel { AlertsViewModel(get(), get()) } viewModel { ConsoleLogViewModel(get()) } + viewModel { DomainConnectionsViewModel(get()) } } val modules = listOf(viewModelModule) diff --git a/app/src/main/res/layout/activity_domain_connections.xml b/app/src/main/res/layout/activity_domain_connections.xml new file mode 100644 index 000000000..8c8649947 --- /dev/null +++ b/app/src/main/res/layout/activity_domain_connections.xml @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 95c5da64a57076a38e0f4899a62bd10f50d91bb2 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 19 Sep 2024 16:47:04 +0530 Subject: [PATCH 128/188] new dimen for sponsor text --- app/src/main/res/values/dimens.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 9119f0b4a..4e999caa9 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -23,6 +23,7 @@ 18sp 22sp 24sp + 72sp 0dp 70dp From 665620aa22f6040d1930a36c15ad0305aac48160 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 19 Sep 2024 16:47:23 +0530 Subject: [PATCH 129/188] frmt code --- .../celzero/bravedns/adapter/RethinkEndpointAdapter.kt | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/RethinkEndpointAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/RethinkEndpointAdapter.kt index 5a397630c..b4cb44d0b 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/RethinkEndpointAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/RethinkEndpointAdapter.kt @@ -205,18 +205,15 @@ class RethinkEndpointAdapter(private val context: Context, private val appConfig builder.setMessage(endpoint.url + "\n\n" + endpoint.desc) builder.setCancelable(true) if (endpoint.isEditable(context)) { - builder.setPositiveButton(context.getString(R.string.rt_edit_dialog_positive)) { _, - _ -> + builder.setPositiveButton(context.getString(R.string.rt_edit_dialog_positive)) { _, _ -> openEditConfiguration(endpoint) } } else { - builder.setPositiveButton(context.getString(R.string.dns_info_positive)) { dialogInterface, - _ -> + builder.setPositiveButton(context.getString(R.string.dns_info_positive)) { dialogInterface, _ -> dialogInterface.dismiss() } } - builder.setNeutralButton(context.getString(R.string.dns_info_neutral)) { _: DialogInterface, - _: Int -> + builder.setNeutralButton(context.getString(R.string.dns_info_neutral)) { _: DialogInterface, _: Int -> clipboardCopy( context, endpoint.url, From d33f5e1484d52766cab02a44782468e16b3bf834 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 19 Sep 2024 18:03:07 +0530 Subject: [PATCH 130/188] show go-version number in about screen --- .../bravedns/scheduler/BugReportZipper.kt | 2 +- .../bravedns/ui/fragment/AboutFragment.kt | 34 +++++++++++++++++-- 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/scheduler/BugReportZipper.kt b/app/src/full/java/com/celzero/bravedns/scheduler/BugReportZipper.kt index 4cd54c35d..ff7638b11 100644 --- a/app/src/full/java/com/celzero/bravedns/scheduler/BugReportZipper.kt +++ b/app/src/full/java/com/celzero/bravedns/scheduler/BugReportZipper.kt @@ -222,7 +222,7 @@ object BugReportZipper { file.appendText(prefsDetails.toString()) val separator = "--------------------------------------------\n" file.appendText(separator) - val build = VpnController.goBuildVersion() + val build = VpnController.goBuildVersion(true) file.appendText(build) file.appendText(separator) } diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/AboutFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/AboutFragment.kt index 15a261885..a198ed6fe 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/AboutFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/AboutFragment.kt @@ -42,6 +42,7 @@ import androidx.work.WorkInfo import androidx.work.WorkManager import by.kirich1409.viewbindingdelegate.viewBinding import com.celzero.bravedns.R +import com.celzero.bravedns.RethinkDnsApplication.Companion.DEBUG import com.celzero.bravedns.databinding.DialogInfoRulesLayoutBinding import com.celzero.bravedns.databinding.DialogViewLogsBinding import com.celzero.bravedns.databinding.DialogWhatsnewBinding @@ -51,6 +52,7 @@ import com.celzero.bravedns.scheduler.BugReportZipper.getZipFileName import com.celzero.bravedns.scheduler.EnhancedBugReport import com.celzero.bravedns.scheduler.WorkScheduler import com.celzero.bravedns.service.AppUpdater +import com.celzero.bravedns.service.PersistentState import com.celzero.bravedns.service.VpnController import com.celzero.bravedns.ui.HomeScreenActivity import com.celzero.bravedns.util.Constants.Companion.INIT_TIME_MS @@ -81,6 +83,7 @@ import java.util.zip.ZipInputStream class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener, KoinComponent { private val b by viewBinding(FragmentAboutBinding::bind) + private val persistentState by inject() private var lastAppExitInfoDialogInvokeTime = INIT_TIME_MS private val workScheduler by inject() @@ -90,11 +93,14 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener, K } private fun initView() { - if (isFdroidFlavour()) { b.aboutAppUpdate.visibility = View.GONE } + updateVersionInfo() + + b.sponsorInfoUsage.text = getSponsorInfo() + b.aboutSponsor.setOnClickListener(this) b.aboutWebsite.setOnClickListener(this) b.aboutTwitter.setOnClickListener(this) @@ -120,16 +126,25 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener, K b.aboutAppContributors.setOnClickListener(this) b.aboutAppTranslate.setOnClickListener(this) b.aboutStats.setOnClickListener(this) + } + private fun updateVersionInfo() { try { - val version = getVersionName() ?: "" + val version = getVersionName() // take first 7 characters of the version name, as the version has build number // appended to it, which is not required for the user to see. - val slicedVersion = version.slice(0..6) ?: "" + val slicedVersion = version.slice(0..6) b.aboutWhatsNew.text = getString(R.string.about_whats_new, slicedVersion) // show the complete version name along with the source of installation b.aboutAppVersion.text = getString(R.string.about_version_install_source, version, getDownloadSource()) + // show the go version if the log level is less than INFO, ie, DEBUG or VERBOSE + if (Logger.LoggerType.fromId(persistentState.goLoggerLevel.toInt()) + .isLessThan(Logger.LoggerType.INFO) + ) { + val build = VpnController.goBuildVersion(false) + b.aboutAppVersion.text = b.aboutAppVersion.text.toString() + "\n" + build + } } catch (e: PackageManager.NameNotFoundException) { Logger.w(LOG_TAG_UI, "package name not found: ${e.message}", e) } @@ -144,6 +159,19 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener, K return pInfo?.versionName ?: "" } + private fun getSponsorInfo(): String { + val installTime = requireContext().packageManager.getPackageInfo( + requireContext().packageName, + 0 + ).firstInstallTime + val timeDiff = System.currentTimeMillis() - installTime + val days = (timeDiff / (1000 * 60 * 60 * 24)).toDouble() + val month = days / 30 + val amount = month * (0.60 + 0.20) + val msg = "You’ve been using Rethink for ${days.toInt()} days, which translates to a usage cost of ${"%.2f".format(amount)}$." + return msg + } + private fun getDownloadSource(): String { if (isFdroidFlavour()) return getString(R.string.build__flavor_fdroid) From 540d20d12e5258251f94f406310e247a852a27f1 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 19 Sep 2024 18:03:55 +0530 Subject: [PATCH 131/188] ui: revamp status updates in wg screen, add dns status check --- .../bravedns/adapter/OneWgConfigAdapter.kt | 131 +++++++++------- .../bravedns/adapter/WgConfigAdapter.kt | 148 +++++++++++------- 2 files changed, 167 insertions(+), 112 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt index c6caa7b88..288f63323 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt @@ -35,6 +35,8 @@ import backend.RouterStats import com.celzero.bravedns.R import com.celzero.bravedns.database.WgConfigFiles import com.celzero.bravedns.databinding.ListItemWgOneInterfaceBinding +import com.celzero.bravedns.net.doh.Transaction +import com.celzero.bravedns.service.PersistentState import com.celzero.bravedns.service.ProxyManager import com.celzero.bravedns.service.VpnController import com.celzero.bravedns.service.WireguardManager @@ -54,7 +56,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -class OneWgConfigAdapter(private val context: Context, private val listener: DnsStatusListener) : +class OneWgConfigAdapter(private val context: Context, private val listener: DnsStatusListener, private val persistentState: PersistentState) : PagingDataAdapter(DIFF_CALLBACK) { private var lifecycleOwner: LifecycleOwner? = null @@ -85,6 +87,15 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns } } + private enum class ProxyStatus(val id: Long) { + TOK(Backend.TOK), + TUP(Backend.TUP), + TZZ(Backend.TZZ), + TNT(Backend.TNT), + TKO(Backend.TKO), + END(Backend.END) + } + override fun onBindViewHolder(holder: WgInterfaceViewHolder, position: Int) { val wgConfigFiles: WgConfigFiles = getItem(position) ?: return holder.update(wgConfigFiles) @@ -191,6 +202,7 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns val pair = VpnController.getSupportedIpVersion(id) val c = WireguardManager.getConfigById(config.id) val stats = VpnController.getProxyStats(id) + val dnsStatusId = VpnController.getDnsStatus(ProxyManager.ID_WG_BASE + config.id) val isSplitTunnel = if (c?.getPeers()?.isNotEmpty() == true) { VpnController.isSplitTunnelProxy(id, pair) @@ -198,74 +210,41 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns false } uiCtx { - updateStatusUi(config, statusId, stats) + updateStatusUi(config, statusId, dnsStatusId, stats) updateProtocolChip(pair) updateSplitTunnelChip(isSplitTunnel) } } - private fun updateStatusUi(config: WgConfigFiles, statusId: Long?, stats: RouterStats?) { + private fun isDnsError(statusId: Long?): Boolean { + if (statusId == null) return true + + val s = Transaction.Status.fromId(statusId) + return s == Transaction.Status.BAD_QUERY || s == Transaction.Status.BAD_RESPONSE || s == Transaction.Status.NO_RESPONSE || s == Transaction.Status.SEND_FAIL || s == Transaction.Status.CLIENT_ERROR || s == Transaction.Status.INTERNAL_ERROR || s == Transaction.Status.TRANSPORT_ERROR + } + + private fun updateStatusUi(config: WgConfigFiles, statusId: Long?, dnsStatusId: Long?, stats: RouterStats?) { if (config.isActive && VpnController.hasTunnel()) { b.interfaceDetailCard.strokeWidth = 2 b.oneWgCheck.isChecked = true b.interfaceAppsCount.visibility = View.VISIBLE b.interfaceAppsCount.text = context.getString(R.string.one_wg_apps_added) - var status: String - val handShakeTime = getHandshakeTime(stats) - if (statusId != null) { - var resId = UIUtils.getProxyStatusStringRes(statusId) - // change the color based on the status - if (statusId == Backend.TOK) { - if (stats?.lastOK == 0L) { - b.interfaceDetailCard.strokeColor = - fetchColor(context, R.attr.chipTextNeutral) - resId = R.string.status_waiting - } else { - b.interfaceDetailCard.strokeColor = - fetchColor(context, R.attr.accentGood) - } - } else if ( - statusId == Backend.TUP || - statusId == Backend.TZZ || - statusId == Backend.TNT - ) { - b.interfaceDetailCard.strokeColor = - fetchColor(context, R.attr.chipTextNeutral) + + if (dnsStatusId != null) { + // check for dns failure cases and update the UI + if (isDnsError(dnsStatusId)) { + b.interfaceDetailCard.strokeColor = fetchColor(context, R.attr.chipTextNegative) + b.interfaceStatus.text = + context.getString(R.string.status_failing).replaceFirstChar(Char::titlecase) } else { - b.interfaceDetailCard.strokeColor = - fetchColor(context, R.attr.chipTextNegative) - } - status = - if (stats?.lastOK == 0L) { - context.getString(resId).replaceFirstChar(Char::titlecase) - } else { - context.getString( - R.string.about_version_install_source, - context.getString(resId).replaceFirstChar(Char::titlecase), - handShakeTime - ) - } - - if ((statusId == Backend.TZZ || statusId == Backend.TNT) && stats != null) { - // for idle state, if lastOk is less than 30 sec, then show as connected - if ( - stats.lastOK != 0L && - System.currentTimeMillis() - stats.lastOK < - 30 * DateUtils.SECOND_IN_MILLIS - ) { - status = - context - .getString(R.string.dns_connected) - .replaceFirstChar(Char::titlecase) - } + // if dns status is not failing, then update the proxy status + updateProxyStatusUi(statusId, stats) } } else { - b.interfaceDetailCard.strokeColor = fetchColor(context, R.attr.chipTextNegative) - b.interfaceDetailCard.strokeWidth = 2 - status = - context.getString(R.string.status_waiting).replaceFirstChar(Char::titlecase) + // in one wg mode, if dns status should be available, this is a fallback case + updateProxyStatusUi(statusId, stats) } - b.interfaceStatus.text = status + b.interfaceActiveLayout.visibility = View.VISIBLE val rxtx = getRxTx(stats) val time = getUpTime(stats) @@ -287,6 +266,48 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns } } + private fun getStrokeColorForStatus(status: ProxyStatus?, stats: RouterStats?): Int{ + return when (status) { + ProxyStatus.TOK -> if (stats?.lastOK == 0L) R.attr.chipTextNeutral else R.attr.accentGood + ProxyStatus.TUP, ProxyStatus.TZZ, ProxyStatus.TNT -> R.attr.chipTextNeutral + else -> R.attr.chipTextNegative + } + } + + private fun getStatusText(status: ProxyStatus?, handshakeTime: String? = null, stats: RouterStats?): String { + if (status == null) return context.getString(R.string.status_waiting).replaceFirstChar(Char::titlecase) + + val baseText = context.getString(UIUtils.getProxyStatusStringRes(status.id)).replaceFirstChar(Char::titlecase) + + return if (stats?.lastOK != 0L && handshakeTime != null) { + context.getString(R.string.about_version_install_source,baseText, handshakeTime) + } else { + baseText + } + } + + private fun getIdleStatusText(status: ProxyStatus?, stats: RouterStats?): String { + if (status != ProxyStatus.TZZ && status != ProxyStatus.TNT) return "" + if (stats == null || stats.lastOK == 0L) return "" + if (System.currentTimeMillis() - stats.lastOK >= 30 * DateUtils.SECOND_IN_MILLIS) return "" + + return context.getString(R.string.dns_connected).replaceFirstChar(Char::titlecase) + } + + private fun updateProxyStatusUi(statusId: Long?, stats: RouterStats?) { + val status = ProxyStatus.entries.find { it.id == statusId } // Convert to enum + + val handshakeTime = getHandshakeTime(stats).toString() + + val strokeColor = getStrokeColorForStatus(status, stats) + b.interfaceDetailCard.strokeColor = fetchColor(context, strokeColor) + + val statusText = getIdleStatusText(status, stats) + .ifEmpty { getStatusText(status, handshakeTime, stats) } + + b.interfaceStatus.text = statusText + } + private fun disableInterface() { b.interfaceDetailCard.strokeWidth = 0 b.protocolInfoChipGroup.visibility = View.GONE diff --git a/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt index 9d473c7c6..07df8d68d 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt @@ -32,9 +32,12 @@ import androidx.recyclerview.widget.RecyclerView import backend.Backend import backend.RouterStats import com.celzero.bravedns.R +import com.celzero.bravedns.adapter.OneWgConfigAdapter.DnsStatusListener import com.celzero.bravedns.database.WgConfigFiles import com.celzero.bravedns.database.WgConfigFilesImmutable import com.celzero.bravedns.databinding.ListItemWgGeneralInterfaceBinding +import com.celzero.bravedns.net.doh.Transaction +import com.celzero.bravedns.service.PersistentState import com.celzero.bravedns.service.ProxyManager import com.celzero.bravedns.service.VpnController import com.celzero.bravedns.service.WireguardManager @@ -45,6 +48,7 @@ import com.celzero.bravedns.service.WireguardManager.ERR_CODE_WG_INVALID import com.celzero.bravedns.ui.activity.WgConfigDetailActivity import com.celzero.bravedns.ui.activity.WgConfigEditorActivity.Companion.INTENT_EXTRA_WG_ID import com.celzero.bravedns.util.UIUtils +import com.celzero.bravedns.util.UIUtils.fetchColor import com.celzero.bravedns.util.Utilities import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -52,7 +56,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -class WgConfigAdapter(private val context: Context) : +class WgConfigAdapter(private val context: Context, private val listener: DnsStatusListener, private val persistentState: PersistentState) : PagingDataAdapter(DIFF_CALLBACK) { private var lifecycleOwner: LifecycleOwner? = null @@ -78,6 +82,15 @@ class WgConfigAdapter(private val context: Context) : } } + private enum class ProxyStatus(val id: Long) { + TOK(Backend.TOK), + TUP(Backend.TUP), + TZZ(Backend.TZZ), + TNT(Backend.TNT), + TKO(Backend.TKO), + END(Backend.END) + } + override fun onBindViewHolder(holder: WgInterfaceViewHolder, position: Int) { val item = getItem(position) val wgConfigFiles: WgConfigFiles = item ?: return @@ -203,6 +216,11 @@ class WgConfigAdapter(private val context: Context) : val pair = VpnController.getSupportedIpVersion(id) val c = WireguardManager.getConfigById(config.id) val stats = VpnController.getProxyStats(id) + val dnsStatusId = if (persistentState.splitDns) { + VpnController.getDnsStatus(id) + } else { + null + } val isSplitTunnel = if (c?.getPeers()?.isNotEmpty() == true) { VpnController.isSplitTunnelProxy(id, pair) @@ -222,7 +240,7 @@ class WgConfigAdapter(private val context: Context) : return } uiCtx { - updateStatusUi(config, statusId, stats) + updateStatusUi(config, statusId, dnsStatusId, stats) updateUi(config, appsCount) updateProtocolChip(pair) updateSplitTunnelChip(isSplitTunnel) @@ -269,17 +287,15 @@ class WgConfigAdapter(private val context: Context) : } } - private fun updateStatusUi(config: WgConfigFiles, statusId: Long?, stats: RouterStats?) { + private fun updateStatusUi(config: WgConfigFiles, statusId: Long?, dnsStatusId: Long?, stats: RouterStats?) { if (config.isActive) { b.interfaceSwitch.isChecked = true b.interfaceDetailCard.strokeWidth = 2 b.interfaceStatus.visibility = View.VISIBLE b.interfaceConfigStatus.visibility = View.VISIBLE - var status: String b.interfaceActiveLayout.visibility = View.VISIBLE val time = getUpTime(stats) val rxtx = getRxTx(stats) - val handShakeTime = getHandshakeTime(stats) if (time.isNotEmpty()) { val t = context.getString(R.string.logs_card_duration, time) b.interfaceActiveUptime.text = @@ -292,65 +308,26 @@ class WgConfigAdapter(private val context: Context) : b.interfaceActiveUptime.text = context.getString(R.string.lbl_active) } b.interfaceActiveRxTx.text = rxtx - if (statusId != null) { - var resId = UIUtils.getProxyStatusStringRes(statusId) - // change the color based on the status - if (statusId == Backend.TOK) { - // if the lastOK is 0, then the handshake is not yet completed - // so show the status as waiting - if (stats?.lastOK == 0L) { - b.interfaceDetailCard.strokeColor = - UIUtils.fetchColor(context, R.attr.chipTextNeutral) - resId = R.string.status_waiting - } else { - b.interfaceDetailCard.strokeColor = - UIUtils.fetchColor(context, R.attr.accentGood) - } - } else if ( - statusId == Backend.TUP || - statusId == Backend.TZZ || - statusId == Backend.TNT - ) { + + if (dnsStatusId != null) { + // check for dns failure cases and update the UI + if (isDnsError(dnsStatusId)) { b.interfaceDetailCard.strokeColor = - UIUtils.fetchColor(context, R.attr.chipTextNeutral) + fetchColor(context, R.attr.chipTextNegative) + b.interfaceStatus.text = + context.getString(R.string.status_failing) + .replaceFirstChar(Char::titlecase) } else { - b.interfaceDetailCard.strokeColor = - UIUtils.fetchColor(context, R.attr.chipTextNegative) - } - status = - if (stats?.lastOK == 0L) { - context.getString(resId).replaceFirstChar(Char::titlecase) - } else { - context.getString( - R.string.about_version_install_source, - context.getString(resId).replaceFirstChar(Char::titlecase), - handShakeTime - ) - } - if ((statusId == Backend.TZZ || statusId == Backend.TNT) && stats != null) { - // for idle state, if lastOk is less than 30 sec, then show as connected - if ( - stats.lastOK != 0L && - System.currentTimeMillis() - stats.lastOK < - 30 * DateUtils.SECOND_IN_MILLIS - ) { - status = - context - .getString(R.string.dns_connected) - .replaceFirstChar(Char::titlecase) - } + // if dns status is not failing, then update the proxy status + updateProxyStatusUi(statusId, stats) } } else { - b.interfaceDetailCard.strokeColor = - UIUtils.fetchColor(context, R.attr.accentBad) - status = - context.getString(R.string.status_waiting).replaceFirstChar(Char::titlecase) - b.interfaceActiveLayout.visibility = View.GONE + // in one wg mode, if dns status should be available, this is a fallback case + updateProxyStatusUi(statusId, stats) } - b.interfaceStatus.text = status } else { b.interfaceActiveLayout.visibility = View.GONE - b.interfaceDetailCard.strokeColor = UIUtils.fetchColor(context, R.attr.background) + b.interfaceDetailCard.strokeColor = fetchColor(context, R.attr.background) b.interfaceDetailCard.strokeWidth = 0 b.interfaceSwitch.isChecked = false b.interfaceConfigStatus.visibility = View.GONE @@ -361,6 +338,61 @@ class WgConfigAdapter(private val context: Context) : } } + private fun getStrokeColorForStatus(status: ProxyStatus?, stats: RouterStats?): Int { + return when (status) { + ProxyStatus.TOK -> if (stats?.lastOK == 0L) R.attr.chipTextNeutral else R.attr.accentGood + ProxyStatus.TUP, ProxyStatus.TZZ, ProxyStatus.TNT -> R.attr.chipTextNeutral + else -> R.attr.chipTextNegative + } + } + + private fun getStatusText( + status: ProxyStatus?, + handshakeTime: String? = null, + stats: RouterStats? + ): String { + if (status == null) return context.getString(R.string.status_waiting) + .replaceFirstChar(Char::titlecase) + + val baseText = context.getString(UIUtils.getProxyStatusStringRes(status.id)) + .replaceFirstChar(Char::titlecase) + + return if (stats?.lastOK != 0L && handshakeTime != null) { + context.getString(R.string.about_version_install_source, baseText, handshakeTime) + } else { + baseText + } + } + + private fun getIdleStatusText(status: ProxyStatus?, stats: RouterStats?): String { + if (status != ProxyStatus.TZZ && status != ProxyStatus.TNT) return "" + if (stats == null || stats.lastOK == 0L) return "" + if (System.currentTimeMillis() - stats.lastOK >= 30 * DateUtils.SECOND_IN_MILLIS) return "" + + return context.getString(R.string.dns_connected).replaceFirstChar(Char::titlecase) + } + + private fun updateProxyStatusUi(statusId: Long?, stats: RouterStats?) { + val status = ProxyStatus.entries.find { it.id == statusId } // Convert to enum + + val handshakeTime = getHandshakeTime(stats).toString() + + val strokeColor = getStrokeColorForStatus(status, stats) + b.interfaceDetailCard.strokeColor = fetchColor(context, strokeColor) + + val statusText = getIdleStatusText(status, stats) + .ifEmpty { getStatusText(status, handshakeTime, stats) } + + b.interfaceStatus.text = statusText + } + + private fun isDnsError(statusId: Long?): Boolean { + if (statusId == null) return true + + val s = Transaction.Status.fromId(statusId) + return s == Transaction.Status.BAD_QUERY || s == Transaction.Status.BAD_RESPONSE || s == Transaction.Status.NO_RESPONSE || s == Transaction.Status.SEND_FAIL || s == Transaction.Status.CLIENT_ERROR || s == Transaction.Status.INTERNAL_ERROR || s == Transaction.Status.TRANSPORT_ERROR + } + private fun getRxTx(stats: RouterStats?): String { if (stats == null) return "" val rx = @@ -453,6 +485,7 @@ class WgConfigAdapter(private val context: Context) : } WireguardManager.disableConfig(cfg) + uiCtx { listener.onDnsStatusChanged() } } private suspend fun enableWgIfPossible(cfg: WgConfigFilesImmutable) { @@ -518,6 +551,7 @@ class WgConfigAdapter(private val context: Context) : } WireguardManager.enableConfig(cfg) + uiCtx { listener.onDnsStatusChanged() } } private fun launchConfigDetail(id: Int) { From 5b8074cada67fdccd6c2ab297d543cd51f3f1658 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 19 Sep 2024 18:04:29 +0530 Subject: [PATCH 132/188] ktfmt: activity_detailed_statistics.xml --- app/src/main/res/layout/activity_detailed_statistics.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/res/layout/activity_detailed_statistics.xml b/app/src/main/res/layout/activity_detailed_statistics.xml index 74c0d133d..d22aa5052 100644 --- a/app/src/main/res/layout/activity_detailed_statistics.xml +++ b/app/src/main/res/layout/activity_detailed_statistics.xml @@ -1,6 +1,4 @@ - - Date: Thu, 19 Sep 2024 19:20:16 +0530 Subject: [PATCH 133/188] move some of the misc settings to advanced settings screen --- .../ui/activity/MiscSettingsActivity.kt | 340 ++++-------------- .../res/layout/activity_misc_settings.xml | 328 ++--------------- 2 files changed, 102 insertions(+), 566 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/MiscSettingsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/MiscSettingsActivity.kt index 923ae287f..394dc736d 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/MiscSettingsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/MiscSettingsActivity.kt @@ -19,7 +19,6 @@ import Logger import Logger.LOG_TAG_APP_OPS import Logger.LOG_TAG_UI import Logger.LOG_TAG_VPN -import Logger.updateConfigLevel import android.Manifest import android.app.LocaleManager import android.content.ActivityNotFoundException @@ -52,7 +51,6 @@ import com.celzero.bravedns.R import com.celzero.bravedns.backup.BackupHelper import com.celzero.bravedns.data.AppConfig import com.celzero.bravedns.databinding.ActivityMiscSettingsBinding -import com.celzero.bravedns.net.go.GoVpnAdapter import com.celzero.bravedns.service.PersistentState import com.celzero.bravedns.ui.bottomsheet.BackupRestoreBottomSheet import com.celzero.bravedns.util.BackgroundAccessibilityService @@ -83,13 +81,24 @@ class MiscSettingsActivity : AppCompatActivity(R.layout.activity_misc_settings) private val b by viewBinding(ActivityMiscSettingsBinding::bind) private val persistentState by inject() - private val appConfig by inject() private lateinit var notificationPermissionResult: ActivityResultLauncher - companion object { - private const val SCHEME_PACKAGE = "package" - private const val STORAGE_PERMISSION_CODE = 23 + enum class BioMetricType(val action: Int, val mins: Long) { + OFF(0, -1L), + IMMEDIATE(1, 0L), + FIVE_MIN(2, 5L), + FIFTEEN_MIN(3, 15L); + + companion object { + fun fromValue(action: Int): BioMetricType { + return entries.firstOrNull { it.action == action } ?: OFF + } + } + + fun enabled(): Boolean { + return this != OFF + } } override fun onCreate(savedInstanceState: Bundle?) { @@ -108,8 +117,6 @@ class MiscSettingsActivity : AppCompatActivity(R.layout.activity_misc_settings) // enable logs b.settingsActivityEnableLogsSwitch.isChecked = persistentState.logsEnabled - // Auto start app after reboot - b.settingsActivityAutoStartSwitch.isChecked = persistentState.prefAutoStartBootUp // check for app updates b.settingsActivityCheckUpdateSwitch.isChecked = persistentState.checkForAppUpdate // camera and microphone access @@ -132,14 +139,14 @@ class MiscSettingsActivity : AppCompatActivity(R.layout.activity_misc_settings) .canAuthenticate(BiometricManager.Authenticators.BIOMETRIC_WEAK) == BiometricManager.BIOMETRIC_SUCCESS ) { - b.settingsBiometricSwitch.isChecked = persistentState.biometricAuth + b.settingsBiometricDesc.text = getString(R.string.settings_biometric_desc) + "; " + + BioMetricType.fromValue(persistentState.biometricAuthType).name } else { b.settingsBiometricRl.visibility = View.GONE } displayAppThemeUi() displayNotificationActionUi() - displayPcapUi() } private fun displayNotificationActionUi() { @@ -173,22 +180,7 @@ class MiscSettingsActivity : AppCompatActivity(R.layout.activity_misc_settings) } } - private fun displayPcapUi() { - b.settingsActivityPcapRl.isEnabled = true - when (PcapMode.getPcapType(persistentState.pcapMode)) { - PcapMode.NONE -> { - b.settingsActivityPcapDesc.text = getString(R.string.settings_pcap_dialog_option_1) - } - PcapMode.LOGCAT -> { - b.settingsActivityPcapDesc.text = getString(R.string.settings_pcap_dialog_option_2) - } - - PcapMode.EXTERNAL_FILE -> { - b.settingsActivityPcapDesc.text = getString(R.string.settings_pcap_dialog_option_3) - } - } - } private fun displayAppThemeUi() { b.settingsActivityThemeRl.isEnabled = true @@ -238,16 +230,6 @@ class MiscSettingsActivity : AppCompatActivity(R.layout.activity_misc_settings) persistentState.logsEnabled = b } - b.settingsActivityAutoStartRl.setOnClickListener { - b.settingsActivityAutoStartSwitch.isChecked = - !b.settingsActivityAutoStartSwitch.isChecked - } - - b.settingsActivityAutoStartSwitch.setOnCheckedChangeListener { _: CompoundButton, b: Boolean - -> - persistentState.prefAutoStartBootUp = b - } - b.settingsActivityCheckUpdateRl.setOnClickListener { b.settingsActivityCheckUpdateSwitch.isChecked = !b.settingsActivityCheckUpdateSwitch.isChecked @@ -263,11 +245,6 @@ class MiscSettingsActivity : AppCompatActivity(R.layout.activity_misc_settings) showThemeDialog() } - b.settingsGoLogRl.setOnClickListener { - enableAfterDelay(500, b.settingsGoLogRl) - showGoLoggerDialog() - } - // Ideally this property should be part of VPN category / section. // As of now the VPN section will be disabled when the // VPN is in lockdown mode. @@ -287,11 +264,6 @@ class MiscSettingsActivity : AppCompatActivity(R.layout.activity_misc_settings) persistentState.persistentNotification = b } - b.settingsActivityPcapRl.setOnClickListener { - enableAfterDelay(TimeUnit.SECONDS.toMillis(1L), b.settingsActivityPcapRl) - showPcapOptionsDialog() - } - b.settingsActivityImportExportRl.setOnClickListener { invokeImportExport() } b.settingsActivityAppNotificationSwitch.setOnClickListener { @@ -303,19 +275,13 @@ class MiscSettingsActivity : AppCompatActivity(R.layout.activity_misc_settings) b.settingsLocaleRl.setOnClickListener { invokeChangeLocaleDialog() } b.settingsBiometricRl.setOnClickListener { - b.settingsBiometricSwitch.isChecked = !b.settingsBiometricSwitch.isChecked + showBiometricDialog() } - b.settingsBiometricSwitch.setOnCheckedChangeListener { _: CompoundButton, checked: Boolean - -> - persistentState.biometricAuth = checked - // Reset the biometric auth time - persistentState.biometricAuthTime = Constants.INIT_TIME_MS + b.settingsBiometricImg.setOnClickListener { + showBiometricDialog() } - b.settingsConsoleLogRl.setOnClickListener { - openConsoleLogActivity() - } b.settingsMicCamAccessRl.setOnClickListener { b.settingsMicCamAccessSwitch.isChecked = !b.settingsMicCamAccessSwitch.isChecked @@ -334,6 +300,53 @@ class MiscSettingsActivity : AppCompatActivity(R.layout.activity_misc_settings) } } + + private fun showBiometricDialog() { + val alertBuilder = MaterialAlertDialogBuilder(this) + alertBuilder.setTitle(getString(R.string.settings_biometric_dialog_heading)) + // show an list of options disable, enable immediate, ask after 5 min, ask after 15 min + val item0 = getString(R.string.settings_biometric_dialog_option_0) + val item1 = getString(R.string.settings_biometric_dialog_option_1) + val item2 = getString(R.string.settings_biometric_dialog_option_2) + val item3 = getString(R.string.settings_biometric_dialog_option_3) + val items = arrayOf(item0, item1, item2, item3) + + val checkedItem = persistentState.biometricAuthType + alertBuilder.setSingleChoiceItems(items, checkedItem) { dialog, which -> + dialog.dismiss() + if (persistentState.biometricAuthType == which) { + return@setSingleChoiceItems + } + + when (BioMetricType.fromValue(which)) { + BioMetricType.OFF -> { + b.settingsBiometricDesc.text = b.settingsBiometricDesc.text.toString() + "; Off" + persistentState.biometricAuthType = BioMetricType.OFF.action + persistentState.biometricAuthTime = Constants.INIT_TIME_MS + } + + BioMetricType.IMMEDIATE -> { + b.settingsBiometricDesc.text = b.settingsBiometricDesc.text.toString() + "; Immediate" + persistentState.biometricAuthType = BioMetricType.IMMEDIATE.action + persistentState.biometricAuthTime = Constants.INIT_TIME_MS + } + + BioMetricType.FIVE_MIN -> { + b.settingsBiometricDesc.text = b.settingsBiometricDesc.text.toString() + "; 5 min" + persistentState.biometricAuthType = BioMetricType.FIVE_MIN.action + persistentState.biometricAuthTime = System.currentTimeMillis() + } + + BioMetricType.FIFTEEN_MIN -> { + b.settingsBiometricDesc.text = b.settingsBiometricDesc.text.toString() + "; 15 min" + persistentState.biometricAuthType = BioMetricType.FIFTEEN_MIN.action + persistentState.biometricAuthTime = System.currentTimeMillis() + } + } + } + alertBuilder.create().show() + } + private fun handleAccessibilityPermission() { try { val isAccessibilityServiceRunning = @@ -426,15 +439,6 @@ class MiscSettingsActivity : AppCompatActivity(R.layout.activity_misc_settings) } } - private fun openConsoleLogActivity() { - try { - val intent = Intent(this, ConsoleLogActivity::class.java) - startActivity(intent) - } catch (e: Exception) { - Logger.e(LOG_TAG_UI, "error opening console log activity ${e.message}", e) - } - } - private fun invokeChangeLocaleDialog() { val alertBuilder = MaterialAlertDialogBuilder(this) alertBuilder.setTitle(getString(R.string.settings_locale_dialog_title)) @@ -566,79 +570,11 @@ class MiscSettingsActivity : AppCompatActivity(R.layout.activity_misc_settings) alertBuilder.create().show() } - private fun showGoLoggerDialog() { - // show dialog with logger options, change log level in GoVpnAdapter based on selection - val alertBuilder = MaterialAlertDialogBuilder(this) - alertBuilder.setTitle(getString(R.string.settings_go_log_heading)) - val items = - arrayOf( - getString(R.string.settings_gologger_dialog_option_0), - getString(R.string.settings_gologger_dialog_option_1), - getString(R.string.settings_gologger_dialog_option_2), - getString(R.string.settings_gologger_dialog_option_3), - getString(R.string.settings_gologger_dialog_option_4), - getString(R.string.settings_gologger_dialog_option_5), - getString(R.string.settings_gologger_dialog_option_6), - getString(R.string.settings_gologger_dialog_option_7) - ) - val checkedItem = persistentState.goLoggerLevel.toInt() - alertBuilder.setSingleChoiceItems(items, checkedItem) { dialog, which -> - dialog.dismiss() - if (checkedItem == which) { - return@setSingleChoiceItems - } - - persistentState.goLoggerLevel = which.toLong() - GoVpnAdapter.setLogLevel(persistentState.goLoggerLevel.toInt()) - updateConfigLevel(persistentState.goLoggerLevel) - } - alertBuilder.create().show() - } - private fun setThemeRecreate(theme: Int) { setTheme(theme) recreate() } - private fun showPcapOptionsDialog() { - val alertBuilder = MaterialAlertDialogBuilder(this) - alertBuilder.setTitle(getString(R.string.settings_pcap_dialog_title)) - val items = - arrayOf( - getString(R.string.settings_pcap_dialog_option_1), - getString(R.string.settings_pcap_dialog_option_2), - getString(R.string.settings_pcap_dialog_option_3) - ) - val checkedItem = persistentState.pcapMode - alertBuilder.setSingleChoiceItems(items, checkedItem) { dialog, which -> - dialog.dismiss() - if (persistentState.pcapMode == which) { - return@setSingleChoiceItems - } - - when (PcapMode.getPcapType(which)) { - PcapMode.NONE -> { - b.settingsActivityPcapDesc.text = - getString(R.string.settings_pcap_dialog_option_1) - appConfig.setPcap(PcapMode.NONE.id) - } - - PcapMode.LOGCAT -> { - b.settingsActivityPcapDesc.text = - getString(R.string.settings_pcap_dialog_option_2) - appConfig.setPcap(PcapMode.LOGCAT.id, PcapMode.ENABLE_PCAP_LOGCAT) - } - - PcapMode.EXTERNAL_FILE -> { - b.settingsActivityPcapDesc.text = - getString(R.string.settings_pcap_dialog_option_3) - createAndSetPcapFile() - } - } - } - alertBuilder.create().show() - } - private fun showNotificationActionDialog() { val alertBuilder = MaterialAlertDialogBuilder(this) alertBuilder.setTitle(getString(R.string.settings_notification_dialog_title)) @@ -717,148 +653,6 @@ class MiscSettingsActivity : AppCompatActivity(R.layout.activity_misc_settings) } } - private val storageActivityResultLauncher: ActivityResultLauncher = - registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { - if (isAtleastR()) { - // version 11 (R) or above - if (Environment.isExternalStorageManager()) { - createAndSetPcapFile() - } else { - showFileCreationErrorToast() - } - } else { - // below ver 11 (R), the permission is handled via onRequestPermissionsResult - } - } - - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray - ) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - if (requestCode == STORAGE_PERMISSION_CODE) { - if (grantResults.isNotEmpty()) { - val write = grantResults[0] == PackageManager.PERMISSION_GRANTED - val read = grantResults[1] == PackageManager.PERMISSION_GRANTED - if (read && write) { - createAndSetPcapFile() - } else { - showFileCreationErrorToast() - } - } - } - } - - private fun requestForStoragePermissions() { - // version 11 (R) or above - if (isAtleastR()) { - try { - val intent = Intent() - intent.action = Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION - val uri = Uri.fromParts(SCHEME_PACKAGE, this.packageName, null) - intent.data = uri - storageActivityResultLauncher.launch(intent) - } catch (e: Exception) { - val intent = Intent() - intent.action = Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION - storageActivityResultLauncher.launch(intent) - } - } else { - // below version 11 - ActivityCompat.requestPermissions( - this, - arrayOf( - Manifest.permission.WRITE_EXTERNAL_STORAGE, - Manifest.permission.READ_EXTERNAL_STORAGE - ), - STORAGE_PERMISSION_CODE - ) - } - } - - private fun showFileCreationErrorToast() { - showToastUiCentered(this, getString(R.string.pcap_failure_toast), Toast.LENGTH_SHORT) - // reset the pcap mode to NONE - persistentState.pcapMode = PcapMode.NONE.id - displayPcapUi() - } - - private fun createAndSetPcapFile() { - // check for storage permissions - if (!checkStoragePermissions()) { - // request for storage permissions - Logger.i(LOG_TAG_VPN, "requesting for storage permissions") - requestForStoragePermissions() - return - } - - Logger.i(LOG_TAG_VPN, "storage permission granted, creating pcap file") - try { - val file = makePcapFile() - if (file == null) { - showFileCreationErrorToast() - return - } - // set the file descriptor instead of fd, need to close the file descriptor - // after tunnel creation - appConfig.setPcap(PcapMode.EXTERNAL_FILE.id, file.absolutePath) - } catch (e: Exception) { - showFileCreationErrorToast() - } - } - - private fun makePcapFile(): File? { - return try { - val sdf = SimpleDateFormat(BackupHelper.BACKUP_FILE_NAME_DATETIME, Locale.ROOT) - // create folder in DOWNLOADS - val dir = - if (isAtleastR()) { - val downloadsDir = - Environment.getExternalStoragePublicDirectory( - Environment.DIRECTORY_DOWNLOADS - ) - // create folder in DOWNLOADS/Rethink - File(downloadsDir, Constants.PCAP_FOLDER_NAME) - } else { - val downloadsDir = Environment.getExternalStorageDirectory() - // create folder in DOWNLOADS/Rethink - File(downloadsDir, Constants.PCAP_FOLDER_NAME) - } - if (!dir.exists()) { - dir.mkdirs() - } - // filename format (rethink_pcap_.pcap) - val pcapFileName: String = - Constants.PCAP_FILE_NAME_PART + sdf.format(Date()) + Constants.PCAP_FILE_EXTENSION - val file = File(dir, pcapFileName) - // just in case, create the parent dir if it doesn't exist - if (file.parentFile?.exists() != true) file.parentFile?.mkdirs() - // create the file if it doesn't exist - if (!file.exists()) { - file.createNewFile() - } - file - } catch (e: Exception) { - Logger.e(LOG_TAG_VPN, "error creating pcap file ${e.message}", e) - null - } - } - - private fun checkStoragePermissions(): Boolean { - return if (isAtleastR()) { - // version 11 (R) or above - Environment.isExternalStorageManager() - } else { - // below version 11 - val write = - ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) - val read = - ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) - read == PackageManager.PERMISSION_GRANTED && write == PackageManager.PERMISSION_GRANTED - } - } - private fun invokeNotificationPermission() { if (!isAtleastT()) { // notification permission is needed for version 13 or above diff --git a/app/src/full/res/layout/activity_misc_settings.xml b/app/src/full/res/layout/activity_misc_settings.xml index cebb7dd82..51e767911 100644 --- a/app/src/full/res/layout/activity_misc_settings.xml +++ b/app/src/full/res/layout/activity_misc_settings.xml @@ -13,272 +13,13 @@ android:orientation="vertical" app:layout_behavior="@string/appbar_scrolling_view_behavior"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + android:src="@drawable/ic_backup_restore" /> - + android:layout_marginEnd="10dp" + android:padding="10dp" + android:src="@drawable/ic_arrow_down_small" /> + android:src="@drawable/ic_logs" /> - + android:padding="10dp" /> + @@ -832,7 +574,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" - android:layout_toStartOf="@id/settings_biometric_switch" + android:layout_toStartOf="@id/settings_biometric_img" android:layout_toEndOf="@id/settings_biometric_icon" android:orientation="vertical"> @@ -853,15 +595,15 @@ android:textSize="@dimen/default_font_text_view" />
- - + android:layout_marginEnd="10dp" + android:padding="10dp" + android:src="@drawable/ic_right_arrow_white" /> @@ -871,8 +613,8 @@ android:layout_height="wrap_content" android:background="?android:attr/selectableItemBackground" android:paddingTop="15dp" - android:visibility="gone" - android:paddingBottom="15dp"> + android:paddingBottom="15dp" + android:visibility="gone"> Date: Sat, 21 Sep 2024 18:07:47 +0530 Subject: [PATCH 134/188] implement task dispatching and channel pooling - added ioDispatcher function to create CoFactory instances and CoroutineScope. - handle task dispatching via a task channel and reply channel pooling. - pooling mechanism for reply channels with a limit of 20 channels. - optimized coroutine task handling using async. --- .../java/com/celzero/bravedns/util/Daemons.kt | 84 ++++++++++++++++++- 1 file changed, 82 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/celzero/bravedns/util/Daemons.kt b/app/src/main/java/com/celzero/bravedns/util/Daemons.kt index 977aa07b0..5ad24bf54 100644 --- a/app/src/main/java/com/celzero/bravedns/util/Daemons.kt +++ b/app/src/main/java/com/celzero/bravedns/util/Daemons.kt @@ -13,9 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.celzero.bravedns.util +package com.celzero.bravedns.util +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.asCoroutineDispatcher +import kotlinx.coroutines.async +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.launch +import kotlinx.coroutines.sync.Mutex import java.util.concurrent.Executors import java.util.concurrent.ThreadFactory import java.util.concurrent.atomic.AtomicInteger @@ -23,11 +32,82 @@ import java.util.concurrent.atomic.AtomicInteger object Daemons { fun make(tag: String) = Executors.newSingleThreadExecutor(Factory(tag)).asCoroutineDispatcher() + fun ioDispatcher(tag: String, default: T, s: CoroutineScope) = CoFactory(tag, default, s, make(tag)) +} + +class CoFactory( + private val tag: String, + private val default: T, + private val scope: CoroutineScope, + private val d: CoroutineDispatcher = Dispatchers.IO +) { + data class Msg(val m: suspend () -> T, val reply: Channel>) + + private val taskChannel = Channel>(Channel.UNLIMITED) + + init { + tasks() + } + + private fun ioAsync(f: suspend () -> T): Deferred { + return scope.async(CoroutineName(tag) + d) { f() } + } + + private fun io(f: suspend () -> Unit) = + scope.launch(CoroutineName(tag) + d) { f() } + + private suspend fun dispatch(f: suspend () -> T): Deferred { + val reply: Channel> = takeChannel() + taskChannel.send(Msg(f, reply)) + val d = reply.receive() + recycleChannel(reply) + return d + } + + suspend fun tryDispatch(f: suspend() -> T): T { + return try { + dispatch(f).await() + } catch (e: Exception) { + Logger.v(Logger.LOG_TAG_VPN, "err in tryDispatch: ${e.message}") + default + } + } + + private fun tasks() = io { + for (task in taskChannel) { + val d = ioAsync(task.m) + task.reply.send(d) + } + } + + // create a stack pooling for reply channels, pool for 20 channels with get and recycle + private val channels = ArrayDeque>>(20) + private val channelsMutex: Mutex = Mutex() + + private suspend fun takeChannel(): Channel> { + channelsMutex.lock() + if (channels.isEmpty()) { + channelsMutex.unlock() + return Channel(Channel.RENDEZVOUS) + } + val x = channels.removeLast() + channelsMutex.unlock() + return x + } + + // always recycle the exhausted channel, ie., the channel that is completed all the receives + private suspend fun recycleChannel(c: Channel>) { + channelsMutex.lock() + if (channels.size < 20) { + channels.add(c) + } + channelsMutex.unlock() + } } // adopted from: java.util.concurrent.Executors.DefaultThreadFactory -class Factory(tag: String = "d"): ThreadFactory { +class Factory(tag: String = "d") : ThreadFactory { private val group: ThreadGroup? private val threadNumber = AtomicInteger(1) private val namePrefix: String From 328d65de2b7f32544e2587dbc83e9a3cb734397c Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 21 Sep 2024 18:17:15 +0530 Subject: [PATCH 135/188] skip listen port based on user setting (randomize listen port) --- app/src/main/java/com/celzero/bravedns/wireguard/Config.kt | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/celzero/bravedns/wireguard/Config.kt b/app/src/main/java/com/celzero/bravedns/wireguard/Config.kt index ebcda39cf..1c9fe9cb1 100644 --- a/app/src/main/java/com/celzero/bravedns/wireguard/Config.kt +++ b/app/src/main/java/com/celzero/bravedns/wireguard/Config.kt @@ -117,10 +117,8 @@ class Config private constructor(builder: Builder) { * * @return the `Config` represented as a series of "key=value" lines */ - fun toWgUserspaceString(isOneWg: Boolean = false): String { - // Skip the listen port if we're in advanced mode. - // In advanced mode, the port may already be in use by another interface. - val skipListenPort = !isOneWg + fun toWgUserspaceString(skipListenPort: Boolean = false): String { + // Skip the listen port if we're in advanced mode or randomize (adv) setting is enabled. val sb = StringBuilder() sb.append(wgInterface?.toWgUserspaceString(skipListenPort) ?: "") sb.append("replace_peers=true\n") From 00208d823ddb3de6c52a7643ba6915db96656d9f Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 21 Sep 2024 18:18:52 +0530 Subject: [PATCH 136/188] Fix: incorrect wg config while fetching catch-all config prefer getting config which doesn't have split tunnel. --- .../bravedns/service/WireguardManager.kt | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt b/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt index 64251e21b..3db9c4ea7 100644 --- a/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt +++ b/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt @@ -988,15 +988,23 @@ object WireguardManager : KoinComponent { suspend fun getOptimalCatchAllConfigId(): Int? { val configs = mappings.filter { it.isCatchAll && it.isActive } - configs.forEach { - if (isValidLastOk(it.id)) { - Logger.d(LOG_TAG_PROXY, "found optimal catch all config: ${it.id}") - return it.id + // get the config which doesn't have split tunnel. If none, return any catch-all config + // TODO: instead get the dns ip from go-lib and check if it can be routed + configs.forEach { config -> + if (isValidLastOk(config.id) && !isSplitTunnelProxy(config.id)) { + Logger.d(LOG_TAG_PROXY, "optimal catch all config found: ${config.id}") + return config.id } } Logger.d(LOG_TAG_PROXY, "no optimal catch all config found, returning any catchall") - // if no catch-all config is active, return any catch-all config - return configs.random()?.id + // return any catch-all config + return configs.randomOrNull()?.id + } + + private suspend fun isSplitTunnelProxy(configId: Int): Boolean { + val id = ProxyManager.ID_WG_BASE + configId + val pair = VpnController.getSupportedIpVersion(id) + return VpnController.isSplitTunnelProxy(id, pair) } private fun io(f: suspend () -> Unit) { From 5302fbc7eeebd251b2564d493b8a617050d11623 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 21 Sep 2024 18:22:22 +0530 Subject: [PATCH 137/188] ui: new settings screen for advanced configuration --- .../ui/activity/AdvancedSettingActivity.kt | 404 +++++++++++++ .../res/layout/activity_advanced_setting.xml | 532 ++++++++++++++++++ 2 files changed, 936 insertions(+) create mode 100644 app/src/full/java/com/celzero/bravedns/ui/activity/AdvancedSettingActivity.kt create mode 100644 app/src/full/res/layout/activity_advanced_setting.xml diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/AdvancedSettingActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/AdvancedSettingActivity.kt new file mode 100644 index 000000000..db5afb51a --- /dev/null +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/AdvancedSettingActivity.kt @@ -0,0 +1,404 @@ +/* + * Copyright 2024 RethinkDNS and its authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.celzero.bravedns.ui.activity + +import Logger.LOG_TAG_UI +import Logger.LOG_TAG_VPN +import Logger.updateConfigLevel +import android.Manifest +import android.content.Context +import android.content.Intent +import android.content.pm.PackageManager +import android.content.res.Configuration +import android.net.Uri +import android.os.Bundle +import android.os.Environment +import android.provider.Settings +import android.view.View +import android.widget.CompoundButton +import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import androidx.lifecycle.lifecycleScope +import by.kirich1409.viewbindingdelegate.viewBinding +import com.celzero.bravedns.R +import com.celzero.bravedns.RethinkDnsApplication.Companion.DEBUG +import com.celzero.bravedns.backup.BackupHelper +import com.celzero.bravedns.data.AppConfig +import com.celzero.bravedns.databinding.ActivityAdvancedSettingBinding +import com.celzero.bravedns.net.go.GoVpnAdapter +import com.celzero.bravedns.service.PersistentState +import com.celzero.bravedns.util.Constants +import com.celzero.bravedns.util.PcapMode +import com.celzero.bravedns.util.Themes +import com.celzero.bravedns.util.Utilities +import com.celzero.bravedns.util.Utilities.isAtleastR +import com.celzero.bravedns.util.Utilities.isAtleastS +import com.celzero.bravedns.util.Utilities.showToastUiCentered +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import java.io.File +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale +import java.util.concurrent.TimeUnit +import org.koin.android.ext.android.inject + +class AdvancedSettingActivity : AppCompatActivity(R.layout.activity_advanced_setting) { + private val persistentState by inject() + private val appConfig by inject() + private val b by viewBinding(ActivityAdvancedSettingBinding::bind) + + private fun Context.isDarkThemeOn(): Boolean { + return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == + Configuration.UI_MODE_NIGHT_YES + } + + companion object { + private const val SCHEME_PACKAGE = "package" + private const val STORAGE_PERMISSION_CODE = 23 + } + + override fun onCreate(savedInstanceState: Bundle?) { + setTheme(Themes.getCurrentTheme(isDarkThemeOn(), persistentState.theme)) + super.onCreate(savedInstanceState) + initView() + setupClickListeners() + } + + private fun initView() { + b.dvWgListenPortSwitch.isChecked = !persistentState.randomizeListenPort + // Auto start app after reboot + b.settingsActivityAutoStartSwitch.isChecked = persistentState.prefAutoStartBootUp + // check if the device is running on Android 12 or above for EIMF + if (isAtleastS()) { + // endpoint independent mapping (eim) / endpoint independent filtering (eif) + b.dvEimfRl.visibility = View.VISIBLE + b.dvEimfSwitch.isChecked = persistentState.endpointIndependence + } else { + b.dvEimfRl.visibility = View.GONE + } + if (DEBUG) { + b.dvTcpKeepAliveRl.visibility = View.VISIBLE + b.dvTcpKeepAliveSwitch.isChecked = persistentState.tcpKeepAlive + } else { + b.dvTcpKeepAliveRl.visibility = View.GONE + } + displayPcapUi() + } + + private fun setupClickListeners() { + + b.dvWgListenPortSwitch.setOnCheckedChangeListener { _, isChecked -> + persistentState.randomizeListenPort = !isChecked + } + + b.dvWgListenPortRl.setOnClickListener { + b.dvWgListenPortSwitch.isChecked = !b.dvWgListenPortSwitch.isChecked + } + + b.dvEimfSwitch.setOnCheckedChangeListener { _, isChecked -> + if (!isAtleastS()) { + return@setOnCheckedChangeListener + } + + persistentState.endpointIndependence = isChecked + } + + b.dvEimfRl.setOnClickListener { b.dvEimfSwitch.isChecked = !b.dvEimfSwitch.isChecked } + + b.settingsAntiCensorshipRl.setOnClickListener { + val intent = Intent(this, AntiCensorshipActivity::class.java) + startActivity(intent) + } + + b.settingsGoLogRl.setOnClickListener { + enableAfterDelay(500, b.settingsGoLogRl) + showGoLoggerDialog() + } + + b.settingsConsoleLogRl.setOnClickListener { openConsoleLogActivity() } + + b.settingsActivityAutoStartRl.setOnClickListener { + b.settingsActivityAutoStartSwitch.isChecked = + !b.settingsActivityAutoStartSwitch.isChecked + } + + b.settingsActivityAutoStartSwitch.setOnCheckedChangeListener { _: CompoundButton, b: Boolean + -> + persistentState.prefAutoStartBootUp = b + } + + b.settingsActivityPcapRl.setOnClickListener { + enableAfterDelay(TimeUnit.SECONDS.toMillis(1L), b.settingsActivityPcapRl) + showPcapOptionsDialog() + } + + b.dvTcpKeepAliveSwitch.setOnCheckedChangeListener { _, isChecked -> + persistentState.tcpKeepAlive = isChecked + } + + b.dvTcpKeepAliveRl.setOnClickListener { b.dvTcpKeepAliveSwitch.isChecked = !b.dvTcpKeepAliveSwitch.isChecked } + } + + private fun displayPcapUi() { + b.settingsActivityPcapRl.isEnabled = true + when (PcapMode.getPcapType(persistentState.pcapMode)) { + PcapMode.NONE -> { + b.settingsActivityPcapDesc.text = getString(R.string.settings_pcap_dialog_option_1) + } + + PcapMode.LOGCAT -> { + b.settingsActivityPcapDesc.text = getString(R.string.settings_pcap_dialog_option_2) + } + + PcapMode.EXTERNAL_FILE -> { + b.settingsActivityPcapDesc.text = getString(R.string.settings_pcap_dialog_option_3) + } + } + } + + private fun showPcapOptionsDialog() { + val alertBuilder = MaterialAlertDialogBuilder(this) + alertBuilder.setTitle(getString(R.string.settings_pcap_dialog_title)) + val items = + arrayOf( + getString(R.string.settings_pcap_dialog_option_1), + getString(R.string.settings_pcap_dialog_option_2), + getString(R.string.settings_pcap_dialog_option_3), + ) + val checkedItem = persistentState.pcapMode + alertBuilder.setSingleChoiceItems(items, checkedItem) { dialog, which -> + dialog.dismiss() + if (persistentState.pcapMode == which) { + return@setSingleChoiceItems + } + + when (PcapMode.getPcapType(which)) { + PcapMode.NONE -> { + b.settingsActivityPcapDesc.text = + getString(R.string.settings_pcap_dialog_option_1) + appConfig.setPcap(PcapMode.NONE.id) + } + + PcapMode.LOGCAT -> { + b.settingsActivityPcapDesc.text = + getString(R.string.settings_pcap_dialog_option_2) + appConfig.setPcap(PcapMode.LOGCAT.id, PcapMode.ENABLE_PCAP_LOGCAT) + } + + PcapMode.EXTERNAL_FILE -> { + b.settingsActivityPcapDesc.text = + getString(R.string.settings_pcap_dialog_option_3) + createAndSetPcapFile() + } + } + } + alertBuilder.create().show() + } + + private fun createAndSetPcapFile() { + // check for storage permissions + if (!checkStoragePermissions()) { + // request for storage permissions + Logger.i(LOG_TAG_VPN, "requesting for storage permissions") + requestForStoragePermissions() + return + } + + Logger.i(LOG_TAG_VPN, "storage permission granted, creating pcap file") + try { + val file = makePcapFile() + if (file == null) { + showFileCreationErrorToast() + return + } + // set the file descriptor instead of fd, need to close the file descriptor + // after tunnel creation + appConfig.setPcap(PcapMode.EXTERNAL_FILE.id, file.absolutePath) + } catch (e: Exception) { + showFileCreationErrorToast() + } + } + + private val storageActivityResultLauncher: ActivityResultLauncher = + registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { + if (isAtleastR()) { + // version 11 (R) or above + if (Environment.isExternalStorageManager()) { + createAndSetPcapFile() + } else { + showFileCreationErrorToast() + } + } else { + // below ver 11 (R), the permission is handled via onRequestPermissionsResult + } + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray, + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (requestCode == STORAGE_PERMISSION_CODE) { + if (grantResults.isNotEmpty()) { + val write = grantResults[0] == PackageManager.PERMISSION_GRANTED + val read = grantResults[1] == PackageManager.PERMISSION_GRANTED + if (read && write) { + createAndSetPcapFile() + } else { + showFileCreationErrorToast() + } + } + } + } + + private fun requestForStoragePermissions() { + // version 11 (R) or above + if (isAtleastR()) { + try { + val intent = Intent() + intent.action = Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION + val uri = Uri.fromParts(SCHEME_PACKAGE, this.packageName, null) + intent.data = uri + storageActivityResultLauncher.launch(intent) + } catch (e: Exception) { + val intent = Intent() + intent.action = Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION + storageActivityResultLauncher.launch(intent) + } + } else { + // below version 11 + ActivityCompat.requestPermissions( + this, + arrayOf( + Manifest.permission.WRITE_EXTERNAL_STORAGE, + Manifest.permission.READ_EXTERNAL_STORAGE, + ), + STORAGE_PERMISSION_CODE, + ) + } + } + + private fun showFileCreationErrorToast() { + showToastUiCentered(this, getString(R.string.pcap_failure_toast), Toast.LENGTH_SHORT) + // reset the pcap mode to NONE + persistentState.pcapMode = PcapMode.NONE.id + displayPcapUi() + } + + private fun makePcapFile(): File? { + return try { + val sdf = SimpleDateFormat(BackupHelper.BACKUP_FILE_NAME_DATETIME, Locale.ROOT) + // create folder in DOWNLOADS + val dir = + if (isAtleastR()) { + val downloadsDir = + Environment.getExternalStoragePublicDirectory( + Environment.DIRECTORY_DOWNLOADS + ) + // create folder in DOWNLOADS/Rethink + File(downloadsDir, Constants.PCAP_FOLDER_NAME) + } else { + val downloadsDir = Environment.getExternalStorageDirectory() + // create folder in DOWNLOADS/Rethink + File(downloadsDir, Constants.PCAP_FOLDER_NAME) + } + if (!dir.exists()) { + dir.mkdirs() + } + // filename format (rethink_pcap_.pcap) + val pcapFileName: String = + Constants.PCAP_FILE_NAME_PART + sdf.format(Date()) + Constants.PCAP_FILE_EXTENSION + val file = File(dir, pcapFileName) + // just in case, create the parent dir if it doesn't exist + if (file.parentFile?.exists() != true) file.parentFile?.mkdirs() + // create the file if it doesn't exist + if (!file.exists()) { + file.createNewFile() + } + file + } catch (e: Exception) { + Logger.e(LOG_TAG_VPN, "error creating pcap file ${e.message}", e) + null + } + } + + private fun checkStoragePermissions(): Boolean { + return if (isAtleastR()) { + // version 11 (R) or above + Environment.isExternalStorageManager() + } else { + // below version 11 + val write = + ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) + val read = + ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) + read == PackageManager.PERMISSION_GRANTED && write == PackageManager.PERMISSION_GRANTED + } + } + + private fun openConsoleLogActivity() { + try { + val intent = Intent(this, ConsoleLogActivity::class.java) + startActivity(intent) + } catch (e: Exception) { + Logger.e(LOG_TAG_UI, "error opening console log activity ${e.message}", e) + } + } + + private fun showGoLoggerDialog() { + // show dialog with logger options, change log level in GoVpnAdapter based on selection + val alertBuilder = MaterialAlertDialogBuilder(this) + alertBuilder.setTitle(getString(R.string.settings_go_log_heading)) + val items = + arrayOf( + getString(R.string.settings_gologger_dialog_option_0), + getString(R.string.settings_gologger_dialog_option_1), + getString(R.string.settings_gologger_dialog_option_2), + getString(R.string.settings_gologger_dialog_option_3), + getString(R.string.settings_gologger_dialog_option_4), + getString(R.string.settings_gologger_dialog_option_5), + getString(R.string.settings_gologger_dialog_option_6), + getString(R.string.settings_gologger_dialog_option_7), + ) + val checkedItem = persistentState.goLoggerLevel.toInt() + alertBuilder.setSingleChoiceItems(items, checkedItem) { dialog, which -> + dialog.dismiss() + if (checkedItem == which) { + return@setSingleChoiceItems + } + + persistentState.goLoggerLevel = which.toLong() + GoVpnAdapter.setLogLevel(persistentState.goLoggerLevel.toInt()) + updateConfigLevel(persistentState.goLoggerLevel) + } + alertBuilder.create().show() + } + + private fun enableAfterDelay(ms: Long, vararg views: View) { + for (v in views) v.isEnabled = false + + Utilities.delay(ms, lifecycleScope) { + if (isFinishing) return@delay + + for (v in views) v.isEnabled = true + } + } +} diff --git a/app/src/full/res/layout/activity_advanced_setting.xml b/app/src/full/res/layout/activity_advanced_setting.xml new file mode 100644 index 000000000..dc9cbc314 --- /dev/null +++ b/app/src/full/res/layout/activity_advanced_setting.xml @@ -0,0 +1,532 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From ee2282d962711e5cdcf1bc6f9815ca199a2b596e Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 21 Sep 2024 19:14:15 +0530 Subject: [PATCH 138/188] ui: new screen for anti-censorship options --- .../ui/activity/AntiCensorshipActivity.kt | 266 ++++++++ .../res/layout/activity_anti_censorship.xml | 607 ++++++++++++++++++ 2 files changed, 873 insertions(+) create mode 100644 app/src/full/java/com/celzero/bravedns/ui/activity/AntiCensorshipActivity.kt create mode 100644 app/src/main/res/layout/activity_anti_censorship.xml diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/AntiCensorshipActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/AntiCensorshipActivity.kt new file mode 100644 index 000000000..c3221e7da --- /dev/null +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/AntiCensorshipActivity.kt @@ -0,0 +1,266 @@ +/* + * Copyright 2024 RethinkDNS and its authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.celzero.bravedns.ui.activity + +import Logger.LOG_TAG_FIREWALL +import android.content.Context +import android.content.res.Configuration +import android.os.Bundle +import android.view.View +import android.widget.CompoundButton +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import by.kirich1409.viewbindingdelegate.viewBinding +import com.celzero.bravedns.R +import com.celzero.bravedns.databinding.ActivityAntiCensorshipBinding +import com.celzero.bravedns.service.PersistentState +import com.celzero.bravedns.util.Themes +import com.celzero.bravedns.util.Utilities +import com.celzero.bravedns.util.Utilities.isAtleastS +import org.koin.android.ext.android.inject +import settings.Settings + +class AntiCensorshipActivity : AppCompatActivity(R.layout.activity_anti_censorship) { + val b by viewBinding(ActivityAntiCensorshipBinding::bind) + + private val persistentState by inject() + + private fun Context.isDarkThemeOn(): Boolean { + return resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK == + Configuration.UI_MODE_NIGHT_YES + } + + enum class DialStrategies(val mode: Int) { + SPLIT_AUTO(Settings.SplitAuto), + SPLIT_TCP(Settings.SplitTCP), + SPLIT_TCP_TLS(Settings.SplitTCPOrTLS), + DESYNC(Settings.SplitDesync), + NEVER_SPLIT(Settings.SplitNever) + } + + enum class RetryStrategies(val mode: Int) { + RETRY_WITH_SPLIT(Settings.RetryWithSplit), + RETRY_NEVER(Settings.RetryNever), + RETRY_AFTER_SPLIT(Settings.RetryAfterSplit) + } + + override fun onCreate(savedInstanceState: Bundle?) { + setTheme(Themes.getCurrentTheme(isDarkThemeOn(), persistentState.theme)) + super.onCreate(savedInstanceState) + initView() + setupClickListeners() + } + + private fun initView() { + updateDialStrategy(persistentState.dialStrategy) + updateRetryStrategy(persistentState.retryStrategy) + } + + private fun updateDialStrategy(selectedState: Int) { + if (!isAtleastS()) { + // desync is not supported in Android 11 and below versions + // so reset the dial strategy to split auto if desync is selected + if (selectedState == DialStrategies.DESYNC.mode) { + persistentState.dialStrategy = DialStrategies.SPLIT_AUTO.mode + b.acRadioDesync.isChecked = false + b.acDesyncRl.visibility = View.GONE + b.acRadioSplitAuto.isChecked = true + Logger.i(LOG_TAG_FIREWALL, "Desync mode is not supported in Android 11 and below") + return + } else { + b.acRadioDesync.isEnabled = false + b.acDesyncRl.visibility = View.GONE + } + } + when (selectedState) { + DialStrategies.NEVER_SPLIT.mode -> { + b.acRadioNeverSplit.isChecked = true + } + DialStrategies.SPLIT_AUTO.mode -> { + b.acRadioSplitAuto.isChecked = true + } + DialStrategies.SPLIT_TCP.mode -> { + b.acRadioSplitTcp.isChecked = true + } + DialStrategies.SPLIT_TCP_TLS.mode -> { + b.acRadioSplitTls.isChecked = true + } + DialStrategies.DESYNC.mode -> { + b.acRadioDesync.isChecked = true + } + } + } + + private fun updateRetryStrategy(selectedState: Int) { + when (selectedState) { + RetryStrategies.RETRY_WITH_SPLIT.mode -> { + b.acRadioRetryWithSplit.isChecked = true + } + RetryStrategies.RETRY_NEVER.mode -> { + b.acRadioNeverRetry.isChecked = true + } + RetryStrategies.RETRY_AFTER_SPLIT.mode -> { + b.acRadioRetryAfterSplit.isChecked = true + } + } + } + + private fun setupClickListeners() { + b.acRadioNeverSplit.setOnCheckedChangeListener { _: CompoundButton, isSelected: Boolean -> + handleAcMode(isSelected, DialStrategies.NEVER_SPLIT.mode) + } + + b.acNeverSplitRl.setOnClickListener { + b.acRadioNeverSplit.isChecked = !b.acRadioNeverSplit.isChecked + } + + b.acRadioSplitAuto.setOnCheckedChangeListener { _: CompoundButton, isSelected: Boolean -> + handleAcMode(isSelected, DialStrategies.SPLIT_AUTO.mode) + } + + b.acRadioSplitTcp.setOnCheckedChangeListener { _: CompoundButton, isSelected: Boolean -> + handleAcMode(isSelected, DialStrategies.SPLIT_TCP.mode) + } + + b.acRadioSplitTls.setOnCheckedChangeListener { _: CompoundButton, isSelected: Boolean -> + handleAcMode(isSelected, DialStrategies.SPLIT_TCP_TLS.mode) + } + + b.acRadioDesync.setOnCheckedChangeListener { _: CompoundButton, isSelected: Boolean -> + handleAcMode(isSelected, DialStrategies.DESYNC.mode) + } + + b.acSplitAutoRl.setOnClickListener { + b.acRadioSplitAuto.isChecked = !b.acRadioSplitAuto.isChecked + } + + b.acSplitTcpRl.setOnClickListener { + b.acRadioSplitTcp.isChecked = !b.acRadioSplitTcp.isChecked + } + + b.acSplitTlsRl.setOnClickListener { + b.acRadioSplitTls.isChecked = !b.acRadioSplitTls.isChecked + } + + b.acDesyncRl.setOnClickListener { + b.acRadioDesync.isChecked = !b.acRadioDesync.isChecked + } + + b.acRadioRetryWithSplit.setOnCheckedChangeListener { _: CompoundButton, isSelected: Boolean -> + handleRetryMode(isSelected, RetryStrategies.RETRY_WITH_SPLIT.mode) + } + + b.acRadioNeverRetry.setOnCheckedChangeListener { _: CompoundButton, isSelected: Boolean -> + handleRetryMode(isSelected, RetryStrategies.RETRY_NEVER.mode) + } + + b.acRadioRetryAfterSplit.setOnCheckedChangeListener { _: CompoundButton, isSelected: Boolean -> + handleRetryMode(isSelected, RetryStrategies.RETRY_AFTER_SPLIT.mode) + } + + b.acRetryWithSplitRl.setOnClickListener { + b.acRadioRetryWithSplit.isChecked = !b.acRadioRetryWithSplit.isChecked + } + + b.acRetryNeverRl.setOnClickListener { + b.acRadioNeverRetry.isChecked = !b.acRadioNeverRetry.isChecked + } + + b.acRetryAfterSplitRl.setOnClickListener { + b.acRadioRetryAfterSplit.isChecked = !b.acRadioRetryAfterSplit.isChecked + } + } + + private fun handleAcMode(isSelected: Boolean, mode: Int) { + if (isSelected) { + persistentState.dialStrategy = mode + disableRadioButtons(mode) + if (mode == DialStrategies.NEVER_SPLIT.mode) { + // disable retry radio buttons for never split + handleRetryMode(true, RetryStrategies.RETRY_NEVER.mode) + } + } else { + // no-op + } + } + + private fun handleRetryMode(isSelected: Boolean, mode: Int) { + var m = mode + if (DialStrategies.NEVER_SPLIT.mode == persistentState.dialStrategy) { + m = RetryStrategies.RETRY_NEVER.mode + Utilities.showToastUiCentered(this, getString(R.string.ac_toast_retry_disabled), Toast.LENGTH_LONG) + } + + if (isSelected) { + persistentState.retryStrategy = m + disableRetryRadioButtons(m) + } else { + // no-op + } + } + + private fun disableRadioButtons(mode: Int) { + when (mode) { + DialStrategies.NEVER_SPLIT.mode -> { + b.acRadioSplitAuto.isChecked = false + b.acRadioSplitTcp.isChecked = false + b.acRadioSplitTls.isChecked = false + b.acRadioDesync.isChecked = false + } + DialStrategies.SPLIT_AUTO.mode -> { + b.acRadioNeverSplit.isChecked = false + b.acRadioSplitTcp.isChecked = false + b.acRadioSplitTls.isChecked = false + b.acRadioDesync.isChecked = false + } + DialStrategies.SPLIT_TCP.mode -> { + b.acRadioNeverSplit.isChecked = false + b.acRadioSplitAuto.isChecked = false + b.acRadioSplitTls.isChecked = false + b.acRadioDesync.isChecked = false + } + DialStrategies.SPLIT_TCP_TLS.mode -> { + b.acRadioNeverSplit.isChecked = false + b.acRadioSplitAuto.isChecked = false + b.acRadioSplitTcp.isChecked = false + b.acRadioDesync.isChecked = false + } + DialStrategies.DESYNC.mode -> { + b.acRadioNeverSplit.isChecked = false + b.acRadioSplitAuto.isChecked = false + b.acRadioSplitTcp.isChecked = false + b.acRadioSplitTls.isChecked = false + } + } + } + + private fun disableRetryRadioButtons(mode: Int) { + when (mode) { + RetryStrategies.RETRY_WITH_SPLIT.mode -> { + b.acRadioNeverRetry.isChecked = false + b.acRadioRetryAfterSplit.isChecked = false + } + RetryStrategies.RETRY_NEVER.mode -> { + b.acRadioRetryWithSplit.isChecked = false + b.acRadioRetryAfterSplit.isChecked = false + } + RetryStrategies.RETRY_AFTER_SPLIT.mode -> { + b.acRadioRetryWithSplit.isChecked = false + b.acRadioNeverRetry.isChecked = false + } + } + } +} diff --git a/app/src/main/res/layout/activity_anti_censorship.xml b/app/src/main/res/layout/activity_anti_censorship.xml new file mode 100644 index 000000000..5911c3e84 --- /dev/null +++ b/app/src/main/res/layout/activity_anti_censorship.xml @@ -0,0 +1,607 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 6559a98b53947e358d46783aebccebeca1705bce Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 21 Sep 2024 19:15:51 +0530 Subject: [PATCH 139/188] ui-fix: update spinner border color in network log bottom sheet --- app/src/main/res/drawable/spinner_outline.xml | 2 +- app/src/main/res/drawable/spinner_outline_with_bg.xml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/drawable/spinner_outline.xml b/app/src/main/res/drawable/spinner_outline.xml index 61fd88b56..3ffbc59f6 100644 --- a/app/src/main/res/drawable/spinner_outline.xml +++ b/app/src/main/res/drawable/spinner_outline.xml @@ -2,6 +2,6 @@ + android:color="?attr/primaryTextColor" /> \ No newline at end of file diff --git a/app/src/main/res/drawable/spinner_outline_with_bg.xml b/app/src/main/res/drawable/spinner_outline_with_bg.xml index 2910898c3..0bf584ecb 100644 --- a/app/src/main/res/drawable/spinner_outline_with_bg.xml +++ b/app/src/main/res/drawable/spinner_outline_with_bg.xml @@ -2,7 +2,7 @@ + android:color="?attr/primaryTextColor" /> \ No newline at end of file From 3cdd425dc7607293a8050e5dfbc9188f60bcb9ae Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 21 Sep 2024 19:16:40 +0530 Subject: [PATCH 140/188] ui-fix: rmv alpha from svg (ic_logs_accent) --- app/src/main/res/drawable/ic_logs_accent.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/res/drawable/ic_logs_accent.xml b/app/src/main/res/drawable/ic_logs_accent.xml index 48339fa5f..aa6db256f 100644 --- a/app/src/main/res/drawable/ic_logs_accent.xml +++ b/app/src/main/res/drawable/ic_logs_accent.xml @@ -1,7 +1,6 @@ Date: Sat, 21 Sep 2024 19:35:12 +0530 Subject: [PATCH 141/188] ui: new dialog prompt for sponsor details --- .../ui/fragment/HomeScreenFragment.kt | 53 ++++++++++++++++--- .../main/res/layout/dialog_sponsor_info.xml | 53 +++++++++++++++++++ 2 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 app/src/main/res/layout/dialog_sponsor_info.xml diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt index f1c94843a..118d86a6e 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt @@ -19,6 +19,7 @@ import Logger import Logger.LOG_TAG_UI import Logger.LOG_TAG_VPN import android.Manifest +import android.annotation.SuppressLint import android.app.Activity import android.app.ActivityManager import android.content.ActivityNotFoundException @@ -38,11 +39,14 @@ import android.os.SystemClock import android.provider.Settings import android.text.format.DateUtils import android.util.TypedValue +import android.view.LayoutInflater import android.view.View import android.widget.Toast import androidx.activity.result.ActivityResult import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts +import androidx.appcompat.widget.AppCompatButton +import androidx.appcompat.widget.AppCompatTextView import androidx.core.content.ContextCompat import androidx.core.net.toUri import androidx.fragment.app.Fragment @@ -194,24 +198,57 @@ class HomeScreenFragment : Fragment(R.layout.fragment_home_screen) { } b.fhsSponsor.setOnClickListener { - val intent = Intent(Intent.ACTION_VIEW, RETHINKDNS_SPONSOR_LINK.toUri()) - startActivity(intent) + promptForAppSponsorship() } b.fhsSponsorBottom.setOnClickListener { - val intent = Intent(Intent.ACTION_VIEW, RETHINKDNS_SPONSOR_LINK.toUri()) - startActivity(intent) + promptForAppSponsorship() } b.fhsTitleRethink.setOnClickListener { - val intent = Intent(Intent.ACTION_VIEW, RETHINKDNS_SPONSOR_LINK.toUri()) - startActivity(intent) + promptForAppSponsorship() } // comment out the below code to disable the alerts card (v0.5.5b) // b.fhsCardAlertsLl.setOnClickListener { startActivity(ScreenType.ALERTS) } } + private fun promptForAppSponsorship() { + val installTime = requireContext().packageManager.getPackageInfo( + requireContext().packageName, + 0 + ).firstInstallTime + val timeDiff = System.currentTimeMillis() - installTime + // convert it to month + val days = (timeDiff / (1000 * 60 * 60 * 24)).toDouble() + val month = days / 30 + // multiply the month with 0.60$ + 0.20$ for every month + val amount = month * (0.60 + 0.20) + Logger.d(LOG_TAG_UI, "Sponsor: $installTime, days/month: $days/$month, amount: $amount") + val alertBuilder = MaterialAlertDialogBuilder(requireContext()) + val inflater = LayoutInflater.from(requireContext()) + val dialogView = inflater.inflate(R.layout.dialog_sponsor_info, null) + alertBuilder.setView(dialogView) + alertBuilder.setCancelable(true) + + val amountTxt = dialogView.findViewById(R.id.dialog_sponsor_info_amount) + val usageTxt = dialogView.findViewById(R.id.dialog_sponsor_info_usage) + val sponsorBtn = dialogView.findViewById(R.id.dialog_sponsor_info_sponsor) + + val dialog = alertBuilder.create() + + val msg = getString(R.string.sponser_dialog_usage_msg, days.toInt().toString(), "%.2f".format(amount)) + amountTxt.text = getString(R.string.two_argument_no_space, getString(R.string.symbol_dollar), "%.2f".format(amount)) + usageTxt.text = msg + + sponsorBtn.setOnClickListener { + val intent = Intent(Intent.ACTION_VIEW, RETHINKDNS_SPONSOR_LINK.toUri()) + startActivity(intent) + } + + dialog.show() + } + private fun handlePause() { if (!VpnController.hasTunnel()) { showToastUiCentered( @@ -554,6 +591,10 @@ class HomeScreenFragment : Fragment(R.layout.fragment_home_screen) { "${ProxyManager.ID_WG_BASE}${id}" } } else { + if (persistentState.splitDns && WireguardManager.isAdvancedWgActive()) { + dns += ", " + resources.getString(R.string.lbl_wireguard) + } + preferredId } diff --git a/app/src/main/res/layout/dialog_sponsor_info.xml b/app/src/main/res/layout/dialog_sponsor_info.xml new file mode 100644 index 000000000..85d9e239d --- /dev/null +++ b/app/src/main/res/layout/dialog_sponsor_info.xml @@ -0,0 +1,53 @@ + + + + + + + + + + From 0ecfbd8d48cbfe17604b994d4574a82d5647cfdb Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 21 Sep 2024 19:36:53 +0530 Subject: [PATCH 142/188] ui-fix: display listen port based on wg mode and user settings --- .../bravedns/ui/activity/WgConfigEditorActivity.kt | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigEditorActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigEditorActivity.kt index e040ede3f..3d00081c9 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigEditorActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigEditorActivity.kt @@ -109,10 +109,7 @@ class WgConfigEditorActivity : AppCompatActivity(R.layout.activity_wg_config_edi wgInterface?.getAddresses()?.joinToString { it.toString() } ) } - if ( - wgInterface?.listenPort?.isPresent == true && - wgInterface?.listenPort?.get() != 1 && wgType.isOneWg() - ) { + if (showListenPort()) { b.listenPortText.setText(wgInterface?.listenPort?.get().toString()) } if (wgInterface?.mtu?.isPresent == true) { @@ -122,6 +119,12 @@ class WgConfigEditorActivity : AppCompatActivity(R.layout.activity_wg_config_edi } } + private fun showListenPort(): Boolean { + val isPresent = wgInterface?.listenPort?.isPresent == true && wgInterface?.listenPort?.get() != 1 + val byType = wgType.isOneWg() || (!persistentState.randomizeListenPort && wgType.isDefault()) + return isPresent && byType + } + private fun setupClickListeners() { b.privateKeyTextLayout.setEndIconOnClickListener { val key = Backend.newWgPrivateKey() From a1f05f4d2f0e842db56f45d243151fe6d514152d Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 21 Sep 2024 20:02:19 +0530 Subject: [PATCH 143/188] ui: display dns name on wg screen (including splitdns) --- .../bravedns/ui/activity/WgMainActivity.kt | 29 +++++++++++-------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/WgMainActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/WgMainActivity.kt index 3368ba77d..bf83bd935 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/WgMainActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/WgMainActivity.kt @@ -174,8 +174,6 @@ class WgMainActivity : observeConfig() observeDnsName() setupClickListeners() - - } private fun setAdapter() { @@ -215,7 +213,7 @@ class WgMainActivity : val layoutManager = LinearLayoutManager(this) b.wgGeneralInterfaceList.layoutManager = layoutManager - wgConfigAdapter = WgConfigAdapter(this) + wgConfigAdapter = WgConfigAdapter(this, this, persistentState) wgConfigViewModel.interfaces.observe(this) { wgConfigAdapter?.submitData(lifecycle, it) } b.wgGeneralInterfaceList.adapter = wgConfigAdapter } @@ -224,7 +222,7 @@ class WgMainActivity : val layoutManager = LinearLayoutManager(this) b.oneWgInterfaceList.layoutManager = layoutManager - oneWgConfigAdapter = OneWgConfigAdapter(this, this) + oneWgConfigAdapter = OneWgConfigAdapter(this, this, persistentState) wgConfigViewModel.interfaces.observe(this) { oneWgConfigAdapter?.submitData(lifecycle, it) } b.oneWgInterfaceList.adapter = oneWgConfigAdapter } @@ -285,18 +283,25 @@ class WgMainActivity : } private fun observeDnsName() { + val activeConfigs = WireguardManager.getEnabledConfigs() if (WireguardManager.oneWireGuardEnabled()) { - val activeConfigs = WireguardManager.getEnabledConfigs() - val isAnyConfigActive = activeConfigs.isNotEmpty() - if (isAnyConfigActive) { - val dnsName = activeConfigs.firstOrNull()?.getName() ?: return - b.wgWireguardDisclaimer.text = getString(R.string.wireguard_disclaimer, dnsName) - } + val dnsName = activeConfigs.firstOrNull()?.getName() ?: return + b.wgWireguardDisclaimer.text = getString(R.string.wireguard_disclaimer, dnsName) // remove the observer if any config is active appConfig.getConnectedDnsObservable().removeObservers(this) } else { - appConfig.getConnectedDnsObservable().observe(this) { - b.wgWireguardDisclaimer.text = getString(R.string.wireguard_disclaimer, it) + appConfig.getConnectedDnsObservable().observe(this) { dns -> + var dnsNames: String = dns.ifEmpty { "" } + if (persistentState.splitDns) { + if (activeConfigs.isNotEmpty()) { + dnsNames += ", " + } + dnsNames += activeConfigs.joinToString(", ") { it.getName() } + b.wgWireguardDisclaimer.text = + getString(R.string.wireguard_disclaimer, dnsNames) + } else { + b.wgWireguardDisclaimer.text = getString(R.string.wireguard_disclaimer, dnsNames) + } } } } From 4ed62ab7c1c19917b3dc6cffc7afbdd1746a9b5b Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 21 Sep 2024 20:06:58 +0530 Subject: [PATCH 144/188] ui-fix: update info icon in Orbot bottom sheet --- app/src/full/res/layout/bottom_sheet_orbot.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/full/res/layout/bottom_sheet_orbot.xml b/app/src/full/res/layout/bottom_sheet_orbot.xml index bc3b71eae..cd8c5f22c 100644 --- a/app/src/full/res/layout/bottom_sheet_orbot.xml +++ b/app/src/full/res/layout/bottom_sheet_orbot.xml @@ -57,7 +57,7 @@ android:layout_marginStart="10dp" android:layout_toEndOf="@id/bs_orbot_app" android:contentDescription="@string/orbot_explanation" - android:src="@drawable/brave_mode_info" /> + android:src="@drawable/ic_info_white" /> Date: Sat, 21 Sep 2024 20:07:48 +0530 Subject: [PATCH 145/188] ui-fix: rmv unnecessary view from app detail screen --- .../full/res/layout/activity_app_details.xml | 30 +++++-------------- 1 file changed, 7 insertions(+), 23 deletions(-) diff --git a/app/src/full/res/layout/activity_app_details.xml b/app/src/full/res/layout/activity_app_details.xml index e85a6f110..abbbf8948 100644 --- a/app/src/full/res/layout/activity_app_details.xml +++ b/app/src/full/res/layout/activity_app_details.xml @@ -92,7 +92,7 @@ android:layout_marginEnd="5dp" android:minWidth="24dp" android:minHeight="24dp" - android:src="@drawable/brave_mode_info" /> + android:src="@drawable/ic_info_white" /> @@ -149,28 +149,12 @@ android:gravity="center" android:weightSum="1"> - - Date: Sat, 21 Sep 2024 20:11:45 +0530 Subject: [PATCH 146/188] stats: display NIC info and RDNS info in the network stat dialog --- app/src/full/java/com/celzero/bravedns/util/UIUtils.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/full/java/com/celzero/bravedns/util/UIUtils.kt b/app/src/full/java/com/celzero/bravedns/util/UIUtils.kt index 16f6d09da..1e79b684b 100644 --- a/app/src/full/java/com/celzero/bravedns/util/UIUtils.kt +++ b/app/src/full/java/com/celzero/bravedns/util/UIUtils.kt @@ -640,8 +640,10 @@ object UIUtils { val fwd = stat?.fwd()?.toString() val icmp = stat?.icmp()?.toString() val nic = stat?.nic()?.toString() + val rdnsInfo = stat?.rdnsinfo()?.toString() + val nicInfo = stat?.nicinfo()?.toString() - var stats = nic + fwd + ip + icmp + tcp + udp + var stats = nic + nicInfo + fwd + ip + icmp + tcp + udp + rdnsInfo stats = stats.replace("{", "\n") stats = stats.replace("}", "\n\n") stats = stats.replace(",", "\n") From 8d374f9fd9e3f037099e2866c813dfcb0f74fafd Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Thu, 26 Sep 2024 21:59:18 +0530 Subject: [PATCH 147/188] fix #1714: add filter for bypass proxy in app, network log screen --- .../adapter/FirewallAppListAdapter.kt | 4 +++ .../bravedns/viewmodel/AppInfoViewModel.kt | 30 +++++++++++++++---- .../celzero/bravedns/database/AppInfoDAO.kt | 30 +++++++++++-------- .../bravedns/database/ConnectionTrackerDAO.kt | 20 +++++++++---- .../bravedns/service/FirewallRuleset.kt | 9 ++++++ 5 files changed, 70 insertions(+), 23 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt index 8f8edcc76..4b97f2609 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/FirewallAppListAdapter.kt @@ -150,6 +150,10 @@ class FirewallAppListAdapter( } private fun maybeDisplayProxyStatus(appInfo: AppInfo) { + if (appInfo.isProxyExcluded) { + return + } + // show key icon in drawable right of b.firewallAppDataUsage val proxy = ProxyManager.getProxyIdForApp(appInfo.uid) if (proxy.isEmpty() || proxy == ID_NONE) { diff --git a/app/src/full/java/com/celzero/bravedns/viewmodel/AppInfoViewModel.kt b/app/src/full/java/com/celzero/bravedns/viewmodel/AppInfoViewModel.kt index c3edbb05d..29001b1b8 100644 --- a/app/src/full/java/com/celzero/bravedns/viewmodel/AppInfoViewModel.kt +++ b/app/src/full/java/com/celzero/bravedns/viewmodel/AppInfoViewModel.kt @@ -56,13 +56,24 @@ class AppInfoViewModel(private val appInfoDAO: AppInfoDAO) : ViewModel() { } } + private fun getBypassProxyFilter(): Set { + val filter = firewallFilter.getFilter() + val bypassFilter = setOf(2, 7) + if (filter == bypassFilter) { + return setOf(1) + } + return setOf(0, 1) + } + private fun allApps(searchString: String): LiveData> { + val includeProxyBypass = getBypassProxyFilter() return if (category.isEmpty()) { Pager(PagingConfig(Constants.LIVEDATA_PAGE_SIZE)) { appInfoDAO.getAppInfos( "%$searchString%", firewallFilter.getFilter(), - firewallFilter.getConnectionStatusFilter() + firewallFilter.getConnectionStatusFilter(), + includeProxyBypass ) } .liveData @@ -73,7 +84,8 @@ class AppInfoViewModel(private val appInfoDAO: AppInfoDAO) : ViewModel() { "%$searchString%", category, firewallFilter.getFilter(), - firewallFilter.getConnectionStatusFilter() + firewallFilter.getConnectionStatusFilter(), + includeProxyBypass ) } .liveData @@ -82,12 +94,14 @@ class AppInfoViewModel(private val appInfoDAO: AppInfoDAO) : ViewModel() { } private fun installedApps(search: String): LiveData> { + val includeProxyBypass = getBypassProxyFilter() return if (category.isEmpty()) { Pager(PagingConfig(Constants.LIVEDATA_PAGE_SIZE)) { appInfoDAO.getInstalledApps( "%$search%", firewallFilter.getFilter(), - firewallFilter.getConnectionStatusFilter() + firewallFilter.getConnectionStatusFilter(), + includeProxyBypass ) } .liveData @@ -98,7 +112,8 @@ class AppInfoViewModel(private val appInfoDAO: AppInfoDAO) : ViewModel() { "%$search%", category, firewallFilter.getFilter(), - firewallFilter.getConnectionStatusFilter() + firewallFilter.getConnectionStatusFilter(), + includeProxyBypass ) } .liveData @@ -107,12 +122,14 @@ class AppInfoViewModel(private val appInfoDAO: AppInfoDAO) : ViewModel() { } private fun systemApps(search: String): LiveData> { + val includeProxyBypass = getBypassProxyFilter() return if (category.isEmpty()) { Pager(PagingConfig(Constants.LIVEDATA_PAGE_SIZE)) { appInfoDAO.getSystemApps( "%$search%", firewallFilter.getFilter(), - firewallFilter.getConnectionStatusFilter() + firewallFilter.getConnectionStatusFilter(), + includeProxyBypass ) } .liveData @@ -123,7 +140,8 @@ class AppInfoViewModel(private val appInfoDAO: AppInfoDAO) : ViewModel() { "%$search%", category, firewallFilter.getFilter(), - firewallFilter.getConnectionStatusFilter() + firewallFilter.getConnectionStatusFilter(), + includeProxyBypass ) } .liveData diff --git a/app/src/main/java/com/celzero/bravedns/database/AppInfoDAO.kt b/app/src/main/java/com/celzero/bravedns/database/AppInfoDAO.kt index 781a6a307..fdb5aed0f 100644 --- a/app/src/main/java/com/celzero/bravedns/database/AppInfoDAO.kt +++ b/app/src/main/java/com/celzero/bravedns/database/AppInfoDAO.kt @@ -54,60 +54,66 @@ interface AppInfoDAO { @Query("select * from AppInfo order by appCategory, uid") fun getAllAppDetails(): List @Query( - "select * from AppInfo where isSystemApp = 1 and (appName like :search or uid like :search or packageName like :search) and firewallStatus in (:firewall) and connectionStatus in (:connectionStatus) order by lower(appName)" + "select * from AppInfo where isSystemApp = 1 and (appName like :search or uid like :search or packageName like :search) and firewallStatus in (:firewall) and connectionStatus in (:connectionStatus) or isProxyExcluded in (:isProxyExcluded) order by lower(appName)" ) fun getSystemApps( search: String, firewall: Set, - connectionStatus: Set + connectionStatus: Set, + isProxyExcluded: Set ): PagingSource @Query( - "select * from AppInfo where isSystemApp = 1 and (appName like :search or uid like :search or packageName like :search) and appCategory in (:filter) and firewallStatus in (:firewall) and connectionStatus in (:connectionStatus) order by lower(appName)" + "select * from AppInfo where isSystemApp = 1 and (appName like :search or uid like :search or packageName like :search) and appCategory in (:filter) and firewallStatus in (:firewall) and connectionStatus in (:connectionStatus) or isProxyExcluded in (:isProxyExcluded) order by lower(appName)" ) fun getSystemApps( search: String, filter: Set, firewall: Set, - connectionStatus: Set + connectionStatus: Set, + isProxyExcluded: Set ): PagingSource @Query( - "select * from AppInfo where isSystemApp = 0 and (appName like :search or uid like :search or packageName like :search) and firewallStatus in (:firewall) and connectionStatus in (:connectionStatus) order by lower(appName)" + "select * from AppInfo where isSystemApp = 0 and (appName like :search or uid like :search or packageName like :search) and firewallStatus in (:firewall) and connectionStatus in (:connectionStatus) or isProxyExcluded in (:isProxyExcluded) order by lower(appName)" ) fun getInstalledApps( search: String, firewall: Set, - connectionStatus: Set + connectionStatus: Set, + isProxyExcluded: Set ): PagingSource @Query( - "select * from AppInfo where isSystemApp = 0 and (appName like :search or uid like :search or packageName like :search) and appCategory in (:filter) and firewallStatus in (:firewall) and connectionStatus in (:connectionStatus) order by lower(appName)" + "select * from AppInfo where isSystemApp = 0 and (appName like :search or uid like :search or packageName like :search) and appCategory in (:filter) and firewallStatus in (:firewall) and connectionStatus in (:connectionStatus) or isProxyExcluded in (:isProxyExcluded) order by lower(appName)" ) fun getInstalledApps( search: String, filter: Set, firewall: Set, - connectionStatus: Set + connectionStatus: Set, + isProxyExcluded: Set ): PagingSource @Query( - "select * from AppInfo where (appName like :search or uid like :search or packageName like :search) and firewallStatus in (:firewall) and connectionStatus in (:connectionStatus) order by lower(appName)" + "select * from AppInfo where (appName like :search or uid like :search or packageName like :search) and firewallStatus in (:firewall) and connectionStatus in (:connectionStatus) or isProxyExcluded in (:isProxyExcluded) order by lower(appName)" ) fun getAppInfos( search: String, firewall: Set, - connectionStatus: Set + connectionStatus: Set, + isProxyExcluded: Set ): PagingSource @Query( - "select * from AppInfo where (appName like :search or uid like :search or packageName like :search) and appCategory in (:filter) and firewallStatus in (:firewall) and connectionStatus in (:connectionStatus) order by lower(appName)" + "select * from AppInfo where (appName like :search or uid like :search or packageName like :search) and appCategory in (:filter) and firewallStatus in (:firewall) and connectionStatus in (:connectionStatus) or isProxyExcluded in (:isProxyExcluded) order by lower(appName)" ) fun getAppInfos( search: String, filter: Set, firewall: Set, - connectionStatus: Set + connectionStatus: Set, + isProxyExcluded: Set ): PagingSource @Query( diff --git a/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerDAO.kt b/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerDAO.kt index dc8a4d3c5..8782ed3cc 100644 --- a/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerDAO.kt +++ b/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerDAO.kt @@ -76,7 +76,7 @@ interface ConnectionTrackerDAO { fun getConnectionTrackerByName(): PagingSource @Query( - "select * from ConnectionTracker where (appName like :query or ipAddress like :query or dnsQuery like :query or flag like :query) order by id desc LIMIT $MAX_LOGS" + "select * from ConnectionTracker where (appName like :query or ipAddress like :query or dnsQuery like :query or flag like :query or proxyDetails like :query) order by id desc LIMIT $MAX_LOGS" ) fun getConnectionTrackerByName(query: String): PagingSource @@ -84,7 +84,7 @@ interface ConnectionTrackerDAO { fun getBlockedConnections(): PagingSource @Query( - "select * from ConnectionTracker where (appName like :query or ipAddress like :query or dnsQuery like :query or flag like :query) and isBlocked = 1 order by id desc LIMIT $MAX_LOGS" + "select * from ConnectionTracker where (appName like :query or ipAddress like :query or dnsQuery like :query or flag like :query or proxyDetails like :query) and isBlocked = 1 order by id desc LIMIT $MAX_LOGS" ) fun getBlockedConnections(query: String): PagingSource @@ -141,7 +141,7 @@ interface ConnectionTrackerDAO { ): PagingSource @Query( - "select * from ConnectionTracker where blockedByRule in (:filter) and isBlocked = 1 and (appName like :query or ipAddress like :query or dnsQuery like :query or flag like :query) order by id desc LIMIT $MAX_LOGS" + "select * from ConnectionTracker where blockedByRule in (:filter) and isBlocked = 1 and (appName like :query or ipAddress like :query or dnsQuery like :query or flag like :query or proxyDetails like :query) order by id desc LIMIT $MAX_LOGS" ) fun getBlockedConnectionsFiltered( query: String, @@ -158,7 +158,7 @@ interface ConnectionTrackerDAO { @Query("DELETE FROM ConnectionTracker WHERE timeStamp < :date") fun purgeLogsByDate(date: Long) @Query( - "select * from ConnectionTracker where isBlocked = 0 and (appName like :query or ipAddress like :query or dnsQuery like :query or flag like :query) order by id desc LIMIT $MAX_LOGS" + "select * from ConnectionTracker where isBlocked = 0 and (appName like :query or ipAddress like :query or dnsQuery like :query or flag like :query or proxyDetails like :query) order by id desc LIMIT $MAX_LOGS" ) fun getAllowedConnections(query: String): PagingSource @@ -171,7 +171,7 @@ interface ConnectionTrackerDAO { fun getAllowedConnectionsFiltered(filter: Set): PagingSource @Query( - "select * from ConnectionTracker where isBlocked = 0 and (appName like :query or ipAddress like :query or dnsQuery like :query or flag like :query) and blockedByRule in (:filter) order by id desc LIMIT $MAX_LOGS" + "select * from ConnectionTracker where isBlocked = 0 and (appName like :query or ipAddress like :query or dnsQuery like :query or flag like :query or proxyDetails like :query) and blockedByRule in (:filter) order by id desc LIMIT $MAX_LOGS" ) fun getAllowedConnectionsFiltered( query: String, @@ -296,4 +296,14 @@ interface ConnectionTrackerDAO { @Query("select * from ConnectionTracker where blockedByRule in ('Rule #1B', 'Rule #1F', 'Rule #3', 'Rule #4', 'Rule #5', 'Rule #6', 'Http block', 'Universal Lockdown')") fun getBlockedUniversalRulesCount(): List + + @Query( + "select uid as uid, '' as ipAddress, 0 as port, COUNT(connId) count, '' as flag, 0 as blocked, appName as appOrDnsName, SUM(uploadBytes) AS uploadBytes, SUM(downloadBytes) AS downloadBytes, 0 as totalBytes from ConnectionTracker where timeStamp > :to and dnsQuery like :query and isBlocked = 0 GROUP BY appName, uid ORDER BY count DESC" + ) + fun getDomainConnections(query: String, to: Long): PagingSource + + @Query( + "select uid as uid, '' as ipAddress, 0 as port, COUNT(connId) count, flag as flag, 0 as blocked, appName as appOrDnsName, SUM(uploadBytes) AS uploadBytes, SUM(downloadBytes) AS downloadBytes, 0 as totalBytes from ConnectionTracker where timeStamp > :to and flag like :query and isBlocked = 0 GROUP BY appName, uid ORDER BY count DESC" + ) + fun getFlagConnections(query: String, to: Long): PagingSource } diff --git a/app/src/main/java/com/celzero/bravedns/service/FirewallRuleset.kt b/app/src/main/java/com/celzero/bravedns/service/FirewallRuleset.kt index 2537c90ea..808e854f5 100644 --- a/app/src/main/java/com/celzero/bravedns/service/FirewallRuleset.kt +++ b/app/src/main/java/com/celzero/bravedns/service/FirewallRuleset.kt @@ -206,6 +206,12 @@ enum class FirewallRuleset(val id: String, val title: Int, val desc: Int, val ac R.string.firewall_rule_private_dns_desc, R.integer.stall ), + RULE15( + "Bypass Proxy", + R.string.firewall_rule_bypass_proxy, + R.string.firewall_rule_bypass_proxy_desc, + R.integer.allow + ), ; companion object { @@ -242,6 +248,7 @@ enum class FirewallRuleset(val id: String, val title: Int, val desc: Int, val ac RULE12.id -> RULE12 RULE13.id -> RULE13 RULE14.id -> RULE14 + RULE15.id -> RULE15 else -> null } } @@ -280,6 +287,7 @@ enum class FirewallRuleset(val id: String, val title: Int, val desc: Int, val ac RULE12.id -> R.drawable.ic_proxy_white RULE13.id -> R.drawable.ic_proxy_white RULE14.id -> R.drawable.bs_dns_home_screen + RULE15.id -> R.drawable.ic_bypass else -> R.drawable.bs_dns_home_screen } } @@ -314,6 +322,7 @@ enum class FirewallRuleset(val id: String, val title: Int, val desc: Int, val ac RULE2F.id -> true RULE2I.id -> true RULE12.id -> true + RULE15.id -> true else -> false } } From 7af13d5480de5a678661b4a18b93ba7c6e4e7283 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 28 Sep 2024 21:25:26 +0530 Subject: [PATCH 148/188] ui: optimize app name retrieval in Network Log adapter --- .../adapter/ConnectionTrackerAdapter.kt | 51 ++++++++----------- 1 file changed, 22 insertions(+), 29 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/ConnectionTrackerAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/ConnectionTrackerAdapter.kt index ad7b40c33..217845c9c 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/ConnectionTrackerAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/ConnectionTrackerAdapter.kt @@ -45,6 +45,7 @@ import com.celzero.bravedns.util.KnownPorts import com.celzero.bravedns.util.Protocol import com.celzero.bravedns.util.UIUtils.getDurationInHumanReadableFormat import com.celzero.bravedns.util.Utilities +import com.celzero.bravedns.util.Utilities.getDefaultIcon import com.celzero.bravedns.util.Utilities.getIcon import com.google.gson.Gson import kotlinx.coroutines.Dispatchers @@ -75,6 +76,7 @@ class ConnectionTrackerAdapter(private val context: Context) : private const val MAX_BYTES = 500000 // 500 KB private const val MAX_TIME_TCP = 135 // seconds private const val MAX_TIME_UDP = 135 // seconds + private const val NO_USER_ID = 0 } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ConnectionTrackerViewHolder { @@ -146,40 +148,31 @@ class ConnectionTrackerAdapter(private val context: Context) : private fun displayAppDetails(ct: ConnectionTracker) { io { uiCtx { - // append the usrId with app name if the usrId is not 0 - // fixme: move the 0 to a constant - if (ct.usrId != 0) { - b.connectionAppName.text = - context.getString( - R.string.about_version_install_source, - ct.appName, - ct.usrId.toString() - ) - } else { - b.connectionAppName.text = ct.appName - } - val apps = FirewallManager.getPackageNamesByUid(ct.uid) + val count = apps.count() - if (apps.isEmpty()) { - loadAppIcon(Utilities.getDefaultIcon(context)) - return@uiCtx - } + val appName = when { + ct.usrId != NO_USER_ID -> context.getString( + R.string.about_version_install_source, + ct.appName, + ct.usrId.toString() + ) - val count = apps.count() - val appName = - if (count > 1) { - context.getString( - R.string.ctbs_app_other_apps, - ct.appName, - (count).minus(1).toString() - ) - } else { - ct.appName - } + count > 1 -> context.getString( + R.string.ctbs_app_other_apps, + ct.appName, + "${count - 1}" + ) + + else -> ct.appName + } b.connectionAppName.text = appName - loadAppIcon(getIcon(context, apps[0], /*No app name */ "")) + if (apps.isEmpty()) { + loadAppIcon(getDefaultIcon(context)) + } else { + loadAppIcon(getIcon(context, apps[0])) + } } } } From f55a92a2817c2fe2d1b28359b666153d1377e60f Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 28 Sep 2024 21:28:40 +0530 Subject: [PATCH 149/188] ui: improve proxy status handling for better UI representation --- .../bravedns/adapter/OneWgConfigAdapter.kt | 25 ++--- .../bravedns/adapter/WgConfigAdapter.kt | 28 ++--- .../ui/activity/WgConfigDetailActivity.kt | 103 ++++++++++++------ .../java/com/celzero/bravedns/util/UIUtils.kt | 9 ++ .../main/res/layout/activity_wg_detail.xml | 1 + 5 files changed, 95 insertions(+), 71 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt index 288f63323..ef3fdb499 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/OneWgConfigAdapter.kt @@ -56,7 +56,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -class OneWgConfigAdapter(private val context: Context, private val listener: DnsStatusListener, private val persistentState: PersistentState) : +class OneWgConfigAdapter(private val context: Context, private val listener: DnsStatusListener) : PagingDataAdapter(DIFF_CALLBACK) { private var lifecycleOwner: LifecycleOwner? = null @@ -87,15 +87,6 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns } } - private enum class ProxyStatus(val id: Long) { - TOK(Backend.TOK), - TUP(Backend.TUP), - TZZ(Backend.TZZ), - TNT(Backend.TNT), - TKO(Backend.TKO), - END(Backend.END) - } - override fun onBindViewHolder(holder: WgInterfaceViewHolder, position: Int) { val wgConfigFiles: WgConfigFiles = getItem(position) ?: return holder.update(wgConfigFiles) @@ -266,15 +257,15 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns } } - private fun getStrokeColorForStatus(status: ProxyStatus?, stats: RouterStats?): Int{ + private fun getStrokeColorForStatus(status: UIUtils.ProxyStatus?, stats: RouterStats?): Int{ return when (status) { - ProxyStatus.TOK -> if (stats?.lastOK == 0L) R.attr.chipTextNeutral else R.attr.accentGood - ProxyStatus.TUP, ProxyStatus.TZZ, ProxyStatus.TNT -> R.attr.chipTextNeutral + UIUtils.ProxyStatus.TOK -> if (stats?.lastOK == 0L) R.attr.chipTextNeutral else R.attr.accentGood + UIUtils.ProxyStatus.TUP, UIUtils.ProxyStatus.TZZ, UIUtils.ProxyStatus.TNT -> R.attr.chipTextNeutral else -> R.attr.chipTextNegative } } - private fun getStatusText(status: ProxyStatus?, handshakeTime: String? = null, stats: RouterStats?): String { + private fun getStatusText(status: UIUtils.ProxyStatus?, handshakeTime: String? = null, stats: RouterStats?): String { if (status == null) return context.getString(R.string.status_waiting).replaceFirstChar(Char::titlecase) val baseText = context.getString(UIUtils.getProxyStatusStringRes(status.id)).replaceFirstChar(Char::titlecase) @@ -286,8 +277,8 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns } } - private fun getIdleStatusText(status: ProxyStatus?, stats: RouterStats?): String { - if (status != ProxyStatus.TZZ && status != ProxyStatus.TNT) return "" + private fun getIdleStatusText(status: UIUtils.ProxyStatus?, stats: RouterStats?): String { + if (status != UIUtils.ProxyStatus.TZZ && status != UIUtils.ProxyStatus.TNT) return "" if (stats == null || stats.lastOK == 0L) return "" if (System.currentTimeMillis() - stats.lastOK >= 30 * DateUtils.SECOND_IN_MILLIS) return "" @@ -295,7 +286,7 @@ class OneWgConfigAdapter(private val context: Context, private val listener: Dns } private fun updateProxyStatusUi(statusId: Long?, stats: RouterStats?) { - val status = ProxyStatus.entries.find { it.id == statusId } // Convert to enum + val status = UIUtils.ProxyStatus.entries.find { it.id == statusId } // Convert to enum val handshakeTime = getHandshakeTime(stats).toString() diff --git a/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt b/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt index 07df8d68d..a7082154f 100644 --- a/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt +++ b/app/src/full/java/com/celzero/bravedns/adapter/WgConfigAdapter.kt @@ -37,7 +37,6 @@ import com.celzero.bravedns.database.WgConfigFiles import com.celzero.bravedns.database.WgConfigFilesImmutable import com.celzero.bravedns.databinding.ListItemWgGeneralInterfaceBinding import com.celzero.bravedns.net.doh.Transaction -import com.celzero.bravedns.service.PersistentState import com.celzero.bravedns.service.ProxyManager import com.celzero.bravedns.service.VpnController import com.celzero.bravedns.service.WireguardManager @@ -56,7 +55,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -class WgConfigAdapter(private val context: Context, private val listener: DnsStatusListener, private val persistentState: PersistentState) : +class WgConfigAdapter(private val context: Context, private val listener: DnsStatusListener, private val splitDns: Boolean) : PagingDataAdapter(DIFF_CALLBACK) { private var lifecycleOwner: LifecycleOwner? = null @@ -82,15 +81,6 @@ class WgConfigAdapter(private val context: Context, private val listener: DnsSta } } - private enum class ProxyStatus(val id: Long) { - TOK(Backend.TOK), - TUP(Backend.TUP), - TZZ(Backend.TZZ), - TNT(Backend.TNT), - TKO(Backend.TKO), - END(Backend.END) - } - override fun onBindViewHolder(holder: WgInterfaceViewHolder, position: Int) { val item = getItem(position) val wgConfigFiles: WgConfigFiles = item ?: return @@ -216,7 +206,7 @@ class WgConfigAdapter(private val context: Context, private val listener: DnsSta val pair = VpnController.getSupportedIpVersion(id) val c = WireguardManager.getConfigById(config.id) val stats = VpnController.getProxyStats(id) - val dnsStatusId = if (persistentState.splitDns) { + val dnsStatusId = if (splitDns) { VpnController.getDnsStatus(id) } else { null @@ -338,16 +328,16 @@ class WgConfigAdapter(private val context: Context, private val listener: DnsSta } } - private fun getStrokeColorForStatus(status: ProxyStatus?, stats: RouterStats?): Int { + private fun getStrokeColorForStatus(status: UIUtils.ProxyStatus?, stats: RouterStats?): Int { return when (status) { - ProxyStatus.TOK -> if (stats?.lastOK == 0L) R.attr.chipTextNeutral else R.attr.accentGood - ProxyStatus.TUP, ProxyStatus.TZZ, ProxyStatus.TNT -> R.attr.chipTextNeutral + UIUtils.ProxyStatus.TOK -> if (stats?.lastOK == 0L) R.attr.chipTextNeutral else R.attr.accentGood + UIUtils.ProxyStatus.TUP, UIUtils.ProxyStatus.TZZ, UIUtils.ProxyStatus.TNT -> R.attr.chipTextNeutral else -> R.attr.chipTextNegative } } private fun getStatusText( - status: ProxyStatus?, + status: UIUtils.ProxyStatus?, handshakeTime: String? = null, stats: RouterStats? ): String { @@ -364,8 +354,8 @@ class WgConfigAdapter(private val context: Context, private val listener: DnsSta } } - private fun getIdleStatusText(status: ProxyStatus?, stats: RouterStats?): String { - if (status != ProxyStatus.TZZ && status != ProxyStatus.TNT) return "" + private fun getIdleStatusText(status: UIUtils.ProxyStatus?, stats: RouterStats?): String { + if (status != UIUtils.ProxyStatus.TZZ && status != UIUtils.ProxyStatus.TNT) return "" if (stats == null || stats.lastOK == 0L) return "" if (System.currentTimeMillis() - stats.lastOK >= 30 * DateUtils.SECOND_IN_MILLIS) return "" @@ -373,7 +363,7 @@ class WgConfigAdapter(private val context: Context, private val listener: DnsSta } private fun updateProxyStatusUi(statusId: Long?, stats: RouterStats?) { - val status = ProxyStatus.entries.find { it.id == statusId } // Convert to enum + val status = UIUtils.ProxyStatus.entries.find { it.id == statusId } // Convert to enum val handshakeTime = getHandshakeTime(stats).toString() diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt index 7c9608897..32ecae9af 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/WgConfigDetailActivity.kt @@ -31,10 +31,8 @@ import backend.Backend import backend.RouterStats import by.kirich1409.viewbindingdelegate.viewBinding import com.celzero.bravedns.R -import com.celzero.bravedns.adapter.WgConfigAdapter import com.celzero.bravedns.adapter.WgIncludeAppsAdapter import com.celzero.bravedns.adapter.WgPeersAdapter -import com.celzero.bravedns.database.WgConfigFiles import com.celzero.bravedns.databinding.ActivityWgDetailBinding import com.celzero.bravedns.service.PersistentState import com.celzero.bravedns.service.ProxyManager @@ -45,11 +43,13 @@ import com.celzero.bravedns.service.WireguardManager.ERR_CODE_VPN_NOT_ACTIVE import com.celzero.bravedns.service.WireguardManager.ERR_CODE_VPN_NOT_FULL import com.celzero.bravedns.service.WireguardManager.ERR_CODE_WG_INVALID import com.celzero.bravedns.service.WireguardManager.INVALID_CONF_ID +import com.celzero.bravedns.ui.activity.NetworkLogsActivity.Companion.RULES_SEARCH_ID_WIREGUARD import com.celzero.bravedns.ui.dialog.WgAddPeerDialog import com.celzero.bravedns.ui.dialog.WgIncludeAppsDialog import com.celzero.bravedns.util.Constants import com.celzero.bravedns.util.Themes import com.celzero.bravedns.util.UIUtils +import com.celzero.bravedns.util.UIUtils.fetchColor import com.celzero.bravedns.util.Utilities import com.celzero.bravedns.viewmodel.ProxyAppsMappingViewModel import com.celzero.bravedns.wireguard.Config @@ -192,52 +192,88 @@ class WgConfigDetailActivity : AppCompatActivity(R.layout.activity_wg_detail) { prefillConfig(config) } - private suspend fun updateStatusUi(id: Int) { val config = WireguardManager.getConfigFilesById(id) val cid = ProxyManager.ID_WG_BASE + id if (config?.isActive == true) { - var status: String val statusId = VpnController.getProxyStatusById(cid) val stats = VpnController.getProxyStats(cid) + val ps = UIUtils.ProxyStatus.entries.find { it.id == statusId } uiCtx { if (statusId != null) { - var resId = UIUtils.getProxyStatusStringRes(statusId) - // change the color based on the status - if (statusId == Backend.TOK) { - // if the lastOK is 0, then the handshake is not yet completed - // so show the status as waiting - if (stats?.lastOK == 0L) { - resId = R.string.status_waiting - } - } - status = getString(resId).replaceFirstChar(Char::titlecase) - - if ((statusId == Backend.TZZ || statusId == Backend.TNT) && stats != null) { - // for idle state, if lastOk is less than 30 sec, then show as connected - if ( - stats.lastOK != 0L && - System.currentTimeMillis() - stats.lastOK < - 30 * DateUtils.SECOND_IN_MILLIS - ) { - status = - getString(R.string.dns_connected).replaceFirstChar(Char::titlecase) - } - } + val handshakeTime = getHandshakeTime(stats).toString() + val statusText = getIdleStatusText(ps, stats) + .ifEmpty { getStatusText(ps, handshakeTime, stats) } + b.statusText.text = statusText } else { - status = getString(R.string.status_waiting).replaceFirstChar(Char::titlecase) + b.statusText.text = + getString(R.string.status_waiting).replaceFirstChar(Char::titlecase) } - b.statusText.text = status + val strokeColor = getStrokeColorForStatus(ps, stats) + b.interfaceDetailCard.strokeWidth = 2 + b.interfaceDetailCard.strokeColor = fetchColor(this, strokeColor) } } else { uiCtx { + b.interfaceDetailCard.strokeWidth = 0 b.statusText.text = getString(R.string.lbl_disabled).replaceFirstChar(Char::titlecase) } } } + private fun getStatusText( + status: UIUtils.ProxyStatus?, + handshakeTime: String? = null, + stats: RouterStats? + ): String { + if (status == null) return getString(R.string.status_waiting) + .replaceFirstChar(Char::titlecase) + + val baseText = getString(UIUtils.getProxyStatusStringRes(status.id)) + .replaceFirstChar(Char::titlecase) + + return if (stats?.lastOK != 0L && handshakeTime != null) { + getString(R.string.about_version_install_source, baseText, handshakeTime) + } else { + baseText + } + } + + private fun getIdleStatusText(status: UIUtils.ProxyStatus?, stats: RouterStats?): String { + if (status != UIUtils.ProxyStatus.TZZ && status != UIUtils.ProxyStatus.TNT) return "" + if (stats == null || stats.lastOK == 0L) return "" + if (System.currentTimeMillis() - stats.lastOK >= 30 * DateUtils.SECOND_IN_MILLIS) return "" + + return getString(R.string.dns_connected).replaceFirstChar(Char::titlecase) + } + + private fun getHandshakeTime(stats: RouterStats?): CharSequence { + if (stats == null) { + return "" + } + if (stats.lastOK == 0L) { + return "" + } + val now = System.currentTimeMillis() + // returns a string describing 'time' as a time relative to 'now' + return DateUtils.getRelativeTimeSpanString( + stats.lastOK, + now, + DateUtils.MINUTE_IN_MILLIS, + DateUtils.FORMAT_ABBREV_RELATIVE + ) + } + + private fun getStrokeColorForStatus(status: UIUtils.ProxyStatus?, stats: RouterStats?): Int { + return when (status) { + UIUtils.ProxyStatus.TOK -> if (stats?.lastOK == 0L) R.attr.chipTextNeutral else R.attr.accentGood + UIUtils.ProxyStatus.TUP, UIUtils.ProxyStatus.TZZ, UIUtils.ProxyStatus.TNT -> R.attr.chipTextNeutral + else -> R.attr.chipTextNegative + } + } + private fun showInvalidConfigDialog() { val builder = MaterialAlertDialogBuilder(this) builder.setTitle(getString(R.string.lbl_wireguard)) @@ -451,18 +487,15 @@ class WgConfigDetailActivity : AppCompatActivity(R.layout.activity_wg_detail) { b.catchAllCheck.setOnClickListener { updateCatchAll(b.catchAllCheck.isChecked) } b.logsBtn.setOnClickListener { - startActivity( - NetworkLogsActivity.Tabs.NETWORK_LOGS.screen, - ProxyManager.ID_WG_BASE + configId - ) + startActivity(ProxyManager.ID_WG_BASE + configId) } } - private fun startActivity(screenToLoad: Int, searchParam: String?) { + private fun startActivity(searchParam: String?) { val intent = Intent(this, NetworkLogsActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED - intent.putExtra(Constants.VIEW_PAGER_SCREEN_TO_LOAD, screenToLoad) - intent.putExtra(Constants.SEARCH_QUERY, searchParam ?: "") + val query = RULES_SEARCH_ID_WIREGUARD + searchParam + intent.putExtra(Constants.SEARCH_QUERY, query) startActivity(intent) } diff --git a/app/src/full/java/com/celzero/bravedns/util/UIUtils.kt b/app/src/full/java/com/celzero/bravedns/util/UIUtils.kt index 1e79b684b..edae9e3ad 100644 --- a/app/src/full/java/com/celzero/bravedns/util/UIUtils.kt +++ b/app/src/full/java/com/celzero/bravedns/util/UIUtils.kt @@ -107,6 +107,15 @@ object UIUtils { } } + enum class ProxyStatus(val id: Long) { + TOK(Backend.TOK), + TUP(Backend.TUP), + TZZ(Backend.TZZ), + TNT(Backend.TNT), + TKO(Backend.TKO), + END(Backend.END) + } + fun formatToRelativeTime(context: Context, timestamp: Long): String { val now = System.currentTimeMillis() return if (DateUtils.isToday(timestamp)) { diff --git a/app/src/main/res/layout/activity_wg_detail.xml b/app/src/main/res/layout/activity_wg_detail.xml index 715e2d71b..31953c44f 100644 --- a/app/src/main/res/layout/activity_wg_detail.xml +++ b/app/src/main/res/layout/activity_wg_detail.xml @@ -109,6 +109,7 @@ android:layout_height="wrap_content" android:layout_marginTop="8dp" android:paddingTop="5dp" + android:textStyle="italic" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/config_title_layout" /> From 450fdb13357702b2677f1eaf4c066289125cb3c9 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 28 Sep 2024 21:31:13 +0530 Subject: [PATCH 150/188] ui: handle all cases in addLoadStateListener for RecyclerView --- .../bravedns/ui/activity/AppInfoActivity.kt | 9 +++++++- .../ui/activity/AppWiseDomainLogsActivity.kt | 7 ++++++ .../ui/activity/AppWiseIpLogsActivity.kt | 6 +++++ .../ui/activity/DetailedStatisticsActivity.kt | 22 +++++++++++++++++++ .../ui/activity/DomainConnectionsActivity.kt | 6 +++++ 5 files changed, 49 insertions(+), 1 deletion(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/AppInfoActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/AppInfoActivity.kt index c61d6f4c5..93fe0c1fc 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/AppInfoActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/AppInfoActivity.kt @@ -408,8 +408,9 @@ class AppInfoActivity : AppCompatActivity(R.layout.activity_app_details) { b.aadMostContactedDomainRl.visibility = View.GONE } else { b.aadMostContactedDomainRl.visibility = View.VISIBLE - } + } else { + b.aadMostContactedDomainRl.visibility = View.VISIBLE } } } @@ -430,6 +431,8 @@ class AppInfoActivity : AppCompatActivity(R.layout.activity_app_details) { } else { b.aadMostContactedDomainRl.visibility = View.VISIBLE } + } else { + b.aadMostContactedDomainRl.visibility = View.VISIBLE } } } @@ -450,6 +453,8 @@ class AppInfoActivity : AppCompatActivity(R.layout.activity_app_details) { } else { b.aadMostContactedIpsRl.visibility = View.VISIBLE } + } else { + b.aadMostContactedIpsRl.visibility = View.VISIBLE } } } @@ -471,6 +476,8 @@ class AppInfoActivity : AppCompatActivity(R.layout.activity_app_details) { } else { b.aadMostContactedIpsRl.visibility = View.VISIBLE } + } else { + b.aadMostContactedIpsRl.visibility = View.VISIBLE } } } diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseDomainLogsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseDomainLogsActivity.kt index f951f042e..c73a0ec26 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseDomainLogsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseDomainLogsActivity.kt @@ -15,6 +15,7 @@ */ package com.celzero.bravedns.ui.activity +import Logger import android.content.Context import android.content.res.ColorStateList import android.content.res.Configuration @@ -207,6 +208,9 @@ class AppWiseDomainLogsActivity : hideNoRulesUi() showRulesUi() } + } else { + hideNoRulesUi() + showRulesUi() } } } @@ -231,6 +235,9 @@ class AppWiseDomainLogsActivity : hideNoRulesUi() showRulesUi() } + } else { + hideNoRulesUi() + showRulesUi() } } } diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseIpLogsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseIpLogsActivity.kt index 1667acd74..9b16e2e09 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseIpLogsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/AppWiseIpLogsActivity.kt @@ -193,6 +193,9 @@ class AppWiseIpLogsActivity : hideNoRulesUi() showRulesUi() } + } else { + hideNoRulesUi() + showRulesUi() } } } @@ -217,6 +220,9 @@ class AppWiseIpLogsActivity : hideNoRulesUi() showRulesUi() } + } else { + hideNoRulesUi() + showRulesUi() } } } diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/DetailedStatisticsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/DetailedStatisticsActivity.kt index 9bf8f7c8b..e7e9c462c 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/DetailedStatisticsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/DetailedStatisticsActivity.kt @@ -1,3 +1,18 @@ +/* + * Copyright 2024 RethinkDNS and its authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.celzero.bravedns.ui.activity import android.content.Context @@ -103,6 +118,7 @@ class DetailedStatisticsActivity : AppCompatActivity(R.layout.activity_detailed_ b.dsaRecycler.layoutManager = layoutManager val recyclerAdapter = SummaryStatisticsAdapter(this, persistentState, appConfig, type) + recyclerAdapter.setTimeCategory(timeCategory) viewModel.timeCategoryChanged(timeCategory) handleStatType(type).observe(this) { recyclerAdapter.submitData(this.lifecycle, it) } @@ -113,7 +129,13 @@ class DetailedStatisticsActivity : AppCompatActivity(R.layout.activity_detailed_ if (recyclerAdapter.itemCount < 1) { b.dsaRecycler.visibility = View.GONE b.dsaNoDataRl.visibility = View.VISIBLE + } else { + b.dsaRecycler.visibility = View.VISIBLE + b.dsaNoDataRl.visibility = View.GONE } + } else { + b.dsaRecycler.visibility = View.VISIBLE + b.dsaNoDataRl.visibility = View.GONE } } b.dsaRecycler.adapter = recyclerAdapter diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/DomainConnectionsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/DomainConnectionsActivity.kt index a341de751..b51a04573 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/DomainConnectionsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/DomainConnectionsActivity.kt @@ -137,7 +137,13 @@ class DomainConnectionsActivity : AppCompatActivity(R.layout.activity_domain_con if (recyclerAdapter.itemCount < 1) { b.dcRecycler.visibility = View.GONE b.dcRecycler.visibility = View.VISIBLE + } else { + b.dcRecycler.visibility = View.VISIBLE + b.dcRecycler.visibility = View.GONE } + } else { + b.dcRecycler.visibility = View.VISIBLE + b.dcRecycler.visibility = View.GONE } } b.dcRecycler.adapter = recyclerAdapter From e36f51644d97302649102f59a6be1f218d62b3b6 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 28 Sep 2024 21:37:58 +0530 Subject: [PATCH 151/188] ui: show app name in search bar hint --- .../ui/fragment/CustomDomainFragment.kt | 21 +++++++++++++++++++ .../bravedns/ui/fragment/CustomIpFragment.kt | 20 ++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/CustomDomainFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/CustomDomainFragment.kt index 5f81aa12a..09fb119ad 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/CustomDomainFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/CustomDomainFragment.kt @@ -29,11 +29,13 @@ import androidx.recyclerview.widget.RecyclerView import by.kirich1409.viewbindingdelegate.viewBinding import com.celzero.bravedns.R import com.celzero.bravedns.adapter.CustomDomainAdapter +import com.celzero.bravedns.database.AppInfo import com.celzero.bravedns.databinding.DialogAddCustomDomainBinding import com.celzero.bravedns.databinding.FragmentCustomDomainBinding import com.celzero.bravedns.service.DomainRulesManager import com.celzero.bravedns.service.DomainRulesManager.isValidDomain import com.celzero.bravedns.service.DomainRulesManager.isWildCardEntry +import com.celzero.bravedns.service.FirewallManager import com.celzero.bravedns.ui.activity.CustomRulesActivity import com.celzero.bravedns.util.Constants.Companion.INTENT_UID import com.celzero.bravedns.util.Constants.Companion.UID_EVERYBODY @@ -108,6 +110,25 @@ class CustomDomainFragment : viewModel.customDomains.observe(this as LifecycleOwner) { adapter.submitData(this.lifecycle, it) } + io { + val appName = FirewallManager.getAppNameByUid(uid) + if (appName != null) { + uiCtx { updateAppNameInSearchHint(appName) } + } + } + } + + private fun updateAppNameInSearchHint(appName: String) { + val appNameTruncated = appName.substring(0, appName.length.coerceAtMost(10)) + val hint = getString( + R.string.two_argument_colon, + appNameTruncated, + getString(R.string.search_custom_domains) + ) + b.cdaSearchView.queryHint = hint + b.cdaSearchView.findViewById(androidx.appcompat.R.id.search_src_text).textSize = + 14f + return } private fun setupAllRules(rule: CustomRulesActivity.RULES) { diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/CustomIpFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/CustomIpFragment.kt index 5b2cadc3d..b88c0db69 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/CustomIpFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/CustomIpFragment.kt @@ -30,6 +30,7 @@ import com.celzero.bravedns.R import com.celzero.bravedns.adapter.CustomIpAdapter import com.celzero.bravedns.databinding.DialogAddCustomIpBinding import com.celzero.bravedns.databinding.FragmentCustomIpBinding +import com.celzero.bravedns.service.FirewallManager import com.celzero.bravedns.service.IpRulesManager import com.celzero.bravedns.ui.activity.CustomRulesActivity import com.celzero.bravedns.util.Constants.Companion.INTENT_UID @@ -141,12 +142,31 @@ class CustomIpFragment : Fragment(R.layout.fragment_custom_ip), SearchView.OnQue if (rules == CustomRulesActivity.RULES.APP_SPECIFIC_RULES) { b.cipAddFab.visibility = View.VISIBLE setupAdapterForApp() + io { + val appName = FirewallManager.getAppNameByUid(uid) + if (!appName.isNullOrEmpty()) { + uiCtx { updateAppNameInSearchHint(appName) } + } + } } else { b.cipAddFab.visibility = View.GONE setupAdapterForAllApps() } } + private fun updateAppNameInSearchHint(appName: String) { + val appNameTruncated = appName.substring(0, appName.length.coerceAtMost(10)) + val hint = getString( + R.string.two_argument_colon, + appNameTruncated, + getString(R.string.search_universal_ips) + ) + b.cipSearchView.queryHint = hint + b.cipSearchView.findViewById(androidx.appcompat.R.id.search_src_text).textSize = + 14f + return + } + private fun setupAdapterForApp() { observeAppSpecificRules() adapter = CustomIpAdapter(requireContext(), CustomRulesActivity.RULES.APP_SPECIFIC_RULES) From c4532441e8584bc9d1fceff3fe55b96b039089df Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 28 Sep 2024 21:38:28 +0530 Subject: [PATCH 152/188] ui: update proper proxy status in home screen tile --- .../bravedns/ui/fragment/HomeScreenFragment.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt index 118d86a6e..511834f22 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/HomeScreenFragment.kt @@ -449,10 +449,17 @@ class HomeScreenFragment : Fragment(R.layout.fragment_home_screen) { val status = VpnController.getProxyStatusById(proxyId) if (status != null) { // consider starting and up as active - if (status == Backend.TKO) { - failing++ + if (status == Backend.TOK) { + val stats = VpnController.getProxyStats(proxyId) + val lastOk = stats?.lastOK ?: 0 + val isUp = System.currentTimeMillis() - lastOk < 30 * DateUtils.SECOND_IN_MILLIS + if (isUp) { + active++ + } else { + failing++ + } } else { - active++ + failing++ } } else { failing++ From 19abb3e1f18033c19e5c207f6bfb668f3e8ce3a8 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 28 Sep 2024 21:39:17 +0530 Subject: [PATCH 153/188] rename, refractor in summary stats fragment --- .../ui/fragment/SummaryStatisticsFragment.kt | 72 ++++++++++++++----- 1 file changed, 56 insertions(+), 16 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/SummaryStatisticsFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/SummaryStatisticsFragment.kt index ea6ea3588..788d576d1 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/SummaryStatisticsFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/SummaryStatisticsFragment.kt @@ -51,6 +51,9 @@ class SummaryStatisticsFragment : Fragment(R.layout.fragment_summary_statistics) private var isVpnActive: Boolean = false + private var contactedDomainsAdapter: SummaryStatisticsAdapter? = null + private var contactedCountriesAdapter: SummaryStatisticsAdapter? = null + enum class SummaryStatisticsType(val tid: Int) { MOST_CONNECTED_APPS(0), MOST_BLOCKED_APPS(1), @@ -114,8 +117,8 @@ class SummaryStatisticsFragment : Fragment(R.layout.fragment_summary_statistics) // get the tabbed view from the view model and set the toggle button // to the selected one. in case of fragment resume, the recycler view // and the toggle button to be in sync - val timeCategory = viewModel.getTimeCategory().value.toString() - val btn = b.toggleGroup.findViewWithTag(timeCategory) + val tc = viewModel.getTimeCategory().value.toString() + val btn = b.toggleGroup.findViewWithTag(tc) btn.isChecked = true handleTotalUsagesUi() } @@ -230,6 +233,8 @@ class SummaryStatisticsFragment : Fragment(R.layout.fragment_summary_statistics) uiCtx { viewModel.timeCategoryChanged(timeCategory, isAppBypassed) } handleTotalUsagesUi() } + contactedDomainsAdapter?.setTimeCategory(timeCategory) + contactedCountriesAdapter?.setTimeCategory(timeCategory) return@OnButtonCheckedListener } @@ -282,8 +287,7 @@ class SummaryStatisticsFragment : Fragment(R.layout.fragment_summary_statistics) requireContext(), persistentState, appConfig, - SummaryStatisticsType.MOST_CONNECTED_APPS - ) + SummaryStatisticsType.MOST_CONNECTED_APPS) viewModel.getAllowedAppNetworkActivity.observe(viewLifecycleOwner) { recyclerAdapter.submitData(viewLifecycleOwner.lifecycle, it) @@ -294,7 +298,11 @@ class SummaryStatisticsFragment : Fragment(R.layout.fragment_summary_statistics) if (it.append.endOfPaginationReached) { if (recyclerAdapter.itemCount < 1) { b.fssAppAllowedLl.visibility = View.GONE + } else { + b.fssAppAllowedLl.visibility = View.VISIBLE } + } else { + b.fssAppAllowedLl.visibility = View.VISIBLE } } @@ -325,7 +333,11 @@ class SummaryStatisticsFragment : Fragment(R.layout.fragment_summary_statistics) if (it.append.endOfPaginationReached) { if (recyclerAdapter.itemCount < 1) { b.fssAppBlockedLl.visibility = View.GONE + } else { + b.fssAppBlockedLl.visibility = View.VISIBLE } + } else { + b.fssAppBlockedLl.visibility = View.VISIBLE } } @@ -346,7 +358,7 @@ class SummaryStatisticsFragment : Fragment(R.layout.fragment_summary_statistics) val layoutManager = CustomLinearLayoutManager(requireContext()) b.fssContactedDomainRecyclerView.layoutManager = layoutManager - val recyclerAdapter = + contactedDomainsAdapter = SummaryStatisticsAdapter( requireContext(), persistentState, @@ -354,21 +366,29 @@ class SummaryStatisticsFragment : Fragment(R.layout.fragment_summary_statistics) SummaryStatisticsType.MOST_CONTACTED_DOMAINS ) + + val timeCategory = viewModel.getTimeCategory() + contactedDomainsAdapter?.setTimeCategory(timeCategory) + viewModel.getMostContactedDomains.observe(viewLifecycleOwner) { - recyclerAdapter.submitData(viewLifecycleOwner.lifecycle, it) + contactedDomainsAdapter?.submitData(viewLifecycleOwner.lifecycle, it) } - recyclerAdapter.addLoadStateListener { + contactedDomainsAdapter?.addLoadStateListener { if (it.append.endOfPaginationReached) { - if (recyclerAdapter.itemCount < 1) { + if (contactedDomainsAdapter!!.itemCount < 1) { b.fssDomainAllowedLl.visibility = View.GONE + } else { + b.fssDomainAllowedLl.visibility = View.VISIBLE } + } else { + b.fssDomainAllowedLl.visibility = View.VISIBLE } } val scale = resources.displayMetrics.density val pixels = ((RECYCLER_ITEM_VIEW_HEIGHT - 60) * scale + 0.5f) b.fssContactedDomainRecyclerView.minimumHeight = pixels.toInt() - b.fssContactedDomainRecyclerView.adapter = recyclerAdapter + b.fssContactedDomainRecyclerView.adapter = contactedDomainsAdapter } private fun showMostBlockedDomains() { @@ -397,7 +417,11 @@ class SummaryStatisticsFragment : Fragment(R.layout.fragment_summary_statistics) if (it.append.endOfPaginationReached) { if (recyclerAdapter.itemCount < 1) { b.fssDomainBlockedLl.visibility = View.GONE + } else { + b.fssDomainBlockedLl.visibility = View.VISIBLE } + } else { + b.fssDomainBlockedLl.visibility = View.VISIBLE } } val scale = resources.displayMetrics.density @@ -432,7 +456,11 @@ class SummaryStatisticsFragment : Fragment(R.layout.fragment_summary_statistics) if (it.append.endOfPaginationReached) { if (recyclerAdapter.itemCount < 1) { b.fssIpAllowedLl.visibility = View.GONE + } else { + b.fssIpAllowedLl.visibility = View.VISIBLE } + } else { + b.fssIpAllowedLl.visibility = View.VISIBLE } } val scale = resources.displayMetrics.density @@ -452,7 +480,7 @@ class SummaryStatisticsFragment : Fragment(R.layout.fragment_summary_statistics) val layoutManager = CustomLinearLayoutManager(requireContext()) b.fssContactedCountriesRecyclerView.layoutManager = layoutManager - val recyclerAdapter = + contactedCountriesAdapter = SummaryStatisticsAdapter( requireContext(), persistentState, @@ -460,20 +488,24 @@ class SummaryStatisticsFragment : Fragment(R.layout.fragment_summary_statistics) SummaryStatisticsType.MOST_CONTACTED_COUNTRIES ) viewModel.getMostContactedCountries.observe(viewLifecycleOwner) { - recyclerAdapter.submitData(viewLifecycleOwner.lifecycle, it) + contactedCountriesAdapter?.submitData(viewLifecycleOwner.lifecycle, it) } - recyclerAdapter.addLoadStateListener { + contactedCountriesAdapter?.addLoadStateListener { if (it.append.endOfPaginationReached) { - if (recyclerAdapter.itemCount < 1) { + if (contactedCountriesAdapter!!.itemCount < 1) { b.fssCountriesAllowedLl.visibility = View.GONE + } else { + b.fssCountriesAllowedLl.visibility = View.VISIBLE } + } else { + b.fssCountriesAllowedLl.visibility = View.VISIBLE } } val scale = resources.displayMetrics.density val pixels = ((RECYCLER_ITEM_VIEW_HEIGHT - 60) * scale + 0.5f) b.fssContactedCountriesRecyclerView.minimumHeight = pixels.toInt() - b.fssContactedCountriesRecyclerView.adapter = recyclerAdapter + b.fssContactedCountriesRecyclerView.adapter = contactedCountriesAdapter } private fun showMostBlockedIps() { @@ -502,7 +534,11 @@ class SummaryStatisticsFragment : Fragment(R.layout.fragment_summary_statistics) if (it.append.endOfPaginationReached) { if (recyclerAdapter.itemCount < 1) { b.fssIpBlockedLl.visibility = View.GONE + } else { + b.fssIpBlockedLl.visibility = View.VISIBLE } + } else { + b.fssIpBlockedLl.visibility = View.VISIBLE } } val scale = resources.displayMetrics.density @@ -518,7 +554,7 @@ class SummaryStatisticsFragment : Fragment(R.layout.fragment_summary_statistics) return } - b.fssContactedCountriesRecyclerView.setHasFixedSize(true) + b.fssCountriesBlockedRecyclerView.setHasFixedSize(true) val layoutManager = CustomLinearLayoutManager(requireContext()) b.fssCountriesBlockedRecyclerView.layoutManager = layoutManager @@ -527,7 +563,7 @@ class SummaryStatisticsFragment : Fragment(R.layout.fragment_summary_statistics) requireContext(), persistentState, appConfig, - SummaryStatisticsType.MOST_CONTACTED_COUNTRIES + SummaryStatisticsType.MOST_BLOCKED_COUNTRIES ) viewModel.getMostBlockedCountries.observe(viewLifecycleOwner) { recyclerAdapter.submitData(viewLifecycleOwner.lifecycle, it) @@ -537,7 +573,11 @@ class SummaryStatisticsFragment : Fragment(R.layout.fragment_summary_statistics) if (it.append.endOfPaginationReached) { if (recyclerAdapter.itemCount < 1) { b.fssCountriesBlockedLl.visibility = View.GONE + } else { + b.fssCountriesBlockedLl.visibility = View.VISIBLE } + } else { + b.fssCountriesBlockedLl.visibility = View.VISIBLE } } val scale = resources.displayMetrics.density From 23cf1042f3be2f030f19f36ce65d61f77e3689e8 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 28 Sep 2024 21:59:23 +0530 Subject: [PATCH 154/188] ui: new setting for undelegated domains in dns configure screen display secondary dns if any proxy dns is enabled. --- .../ui/fragment/DnsSettingsFragment.kt | 127 ++++-- .../res/layout/fragment_dns_configure.xml | 424 +++++++++++++----- 2 files changed, 397 insertions(+), 154 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/DnsSettingsFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/DnsSettingsFragment.kt index d7762e520..a48c6457b 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/DnsSettingsFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/DnsSettingsFragment.kt @@ -55,8 +55,7 @@ import org.koin.android.ext.android.get import org.koin.android.ext.android.inject import java.util.concurrent.TimeUnit -class DnsSettingsFragment : - Fragment(R.layout.fragment_dns_configure), +class DnsSettingsFragment : Fragment(R.layout.fragment_dns_configure), LocalBlocklistsBottomSheet.OnBottomSheetDialogFragmentDismiss { private val b by viewBinding(FragmentDnsConfigureBinding::bind) @@ -99,18 +98,21 @@ class DnsSettingsFragment : b.dcFaviconSwitch.isChecked = persistentState.fetchFavIcon // prevent dns leaks b.dcPreventDnsLeaksSwitch.isChecked = persistentState.preventDnsLeaks + // enable per-app domain rules (dns alg) + b.dcAlgSwitch.isChecked = persistentState.enableDnsAlg // periodically check for blocklist update b.dcCheckUpdateSwitch.isChecked = persistentState.periodicallyCheckBlocklistUpdate // use custom download manager b.dcDownloaderSwitch.isChecked = persistentState.useCustomDownloadManager - // enable per-app domain rules (dns alg) - b.dcAlgSwitch.isChecked = persistentState.enableDnsAlg // enable dns caching in tunnel b.dcEnableCacheSwitch.isChecked = persistentState.enableDnsCache // proxy dns b.dcProxyDnsSwitch.isChecked = !persistentState.proxyDns - + // use system dns for undelegated domains + b.dcUndelegatedDomainsSwitch.isChecked = persistentState.useSystemDnsForUndelegatedDomains b.connectedStatusTitle.text = getConnectedDnsType() + b.dvBypassDnsBlockSwitch.isChecked = persistentState.bypassBlockInDns + updateSpiltDnsUi() } private fun updateLocalBlocklistUi() { @@ -137,11 +139,6 @@ class DnsSettingsFragment : } private fun initObservers() { - observeBraveMode() - observeAppState() - } - - private fun observeAppState() { VpnController.connectionStatus.observe(viewLifecycleOwner) { if (it == BraveVPNService.State.PAUSED) { val intent = Intent(requireContext(), PauseActivity::class.java) @@ -151,6 +148,17 @@ class DnsSettingsFragment : appConfig.getConnectedDnsObservable().observe(viewLifecycleOwner) { updateConnectedStatus(it) + updateSelectedDns() + } + } + + private fun updateSpiltDnsUi() { + if (persistentState.enableDnsAlg) { + b.dcSplitDnsRl.visibility = View.VISIBLE + b.dcSplitDnsSwitch.isChecked = persistentState.splitDns + } else { + b.dcSplitDnsRl.visibility = View.GONE + b.dcSplitDnsSwitch.isChecked = false } } @@ -159,44 +167,47 @@ class DnsSettingsFragment : b.connectedStatusTitleUrl.text = resources.getString(R.string.configure_dns_connected_dns_proxy_status) b.connectedStatusTitle.text = resources.getString(R.string.lbl_wireguard) - disableAllDns() - b.wireguardRb.isEnabled = true return } + var dns = connectedDns + if (persistentState.splitDns && WireguardManager.isAdvancedWgActive()) { + dns += ", " + resources.getString(R.string.lbl_wireguard) + } + when (appConfig.getDnsType()) { AppConfig.DnsType.DOH -> { b.connectedStatusTitleUrl.text = resources.getString(R.string.configure_dns_connected_doh_status) - b.connectedStatusTitle.text = connectedDns + b.connectedStatusTitle.text = dns } AppConfig.DnsType.DOT -> { b.connectedStatusTitleUrl.text = resources.getString(R.string.lbl_dot) - b.connectedStatusTitle.text = connectedDns + b.connectedStatusTitle.text = dns } AppConfig.DnsType.DNSCRYPT -> { b.connectedStatusTitleUrl.text = resources.getString(R.string.configure_dns_connected_dns_crypt_status) - b.connectedStatusTitle.text = connectedDns + b.connectedStatusTitle.text = dns } AppConfig.DnsType.DNS_PROXY -> { b.connectedStatusTitleUrl.text = resources.getString(R.string.configure_dns_connected_dns_proxy_status) - b.connectedStatusTitle.text = connectedDns + b.connectedStatusTitle.text = dns } AppConfig.DnsType.RETHINK_REMOTE -> { b.connectedStatusTitleUrl.text = resources.getString(R.string.configure_dns_connected_doh_status) - b.connectedStatusTitle.text = connectedDns + b.connectedStatusTitle.text = dns } AppConfig.DnsType.SYSTEM_DNS -> { b.connectedStatusTitleUrl.text = resources.getString(R.string.configure_dns_connected_dns_proxy_status) - b.connectedStatusTitle.text = connectedDns + b.connectedStatusTitle.text = dns } AppConfig.DnsType.ODOH -> { b.connectedStatusTitleUrl.text = resources.getString(R.string.lbl_odoh) - b.connectedStatusTitle.text = connectedDns + b.connectedStatusTitle.text = dns } } } @@ -213,25 +224,30 @@ class DnsSettingsFragment : if (WireguardManager.oneWireGuardEnabled()) { b.wireguardRb.visibility = View.VISIBLE b.wireguardRb.isChecked = true + b.wireguardRb.setChecked(true) b.wireguardRb.isEnabled = true disableAllDns() return - } else { - b.wireguardRb.visibility = View.GONE } + b.wireguardRb.visibility = View.GONE if (isSystemDns()) { b.networkDnsRb.isChecked = true - return - } - - if (isRethinkDns()) { + b.rethinkPlusDnsRb.isChecked = false + b.customDnsRb.isChecked = false + b.networkDnsRb.setChecked(true) + } else if (isRethinkDns()) { b.rethinkPlusDnsRb.isChecked = true - return + b.customDnsRb.isChecked = false + b.networkDnsRb.isChecked = false + b.rethinkPlusDnsRb.setChecked(true) + } else { + // connected to custom dns, update the dns details + b.customDnsRb.isChecked = true + b.rethinkPlusDnsRb.isChecked = false + b.networkDnsRb.isChecked = false + b.customDnsRb.setChecked(true) } - - // connected to custom dns, update the dns details - b.customDnsRb.isChecked = true } private fun disableAllDns() { @@ -272,10 +288,6 @@ class DnsSettingsFragment : } } - private fun observeBraveMode() { - appConfig.getConnectedDnsObservable().observe(viewLifecycleOwner) { updateSelectedDns() } - } - private fun initClickListeners() { b.dcLocalBlocklistRl.setOnClickListener { openLocalBlocklist() } @@ -286,13 +298,6 @@ class DnsSettingsFragment : b.dcCheckUpdateSwitch.isChecked = !b.dcCheckUpdateSwitch.isChecked } - b.dcAlgSwitch.setOnCheckedChangeListener { _: CompoundButton, enabled: Boolean -> - enableAfterDelay(TimeUnit.SECONDS.toMillis(1), b.dcAlgSwitch) - persistentState.enableDnsAlg = enabled - } - - b.dcAlgRl.setOnClickListener { b.dcAlgSwitch.isChecked = !b.dcAlgSwitch.isChecked } - b.dcCheckUpdateSwitch.setOnCheckedChangeListener { _: CompoundButton, enabled: Boolean -> persistentState.periodicallyCheckBlocklistUpdate = enabled if (enabled) { @@ -307,6 +312,14 @@ class DnsSettingsFragment : } } + b.dcAlgSwitch.setOnCheckedChangeListener { _: CompoundButton, enabled: Boolean -> + enableAfterDelay(TimeUnit.SECONDS.toMillis(1), b.dcAlgSwitch) + persistentState.enableDnsAlg = enabled + updateSpiltDnsUi() + } + + b.dcAlgRl.setOnClickListener { b.dcAlgSwitch.isChecked = !b.dcAlgSwitch.isChecked } + b.dcFaviconRl.setOnClickListener { b.dcFaviconSwitch.isChecked = !b.dcFaviconSwitch.isChecked } @@ -326,16 +339,19 @@ class DnsSettingsFragment : persistentState.preventDnsLeaks = enabled } + b.rethinkPlusDnsRb.setOnCheckedChangeListener(null) b.rethinkPlusDnsRb.setOnClickListener { // rethink dns plus invokeRethinkActivity(ConfigureRethinkBasicActivity.FragmentLoader.DB_LIST) } + b.customDnsRb.setOnCheckedChangeListener(null) b.customDnsRb.setOnClickListener { // custom dns showCustomDns() } + b.networkDnsRb.setOnCheckedChangeListener(null) b.networkDnsRb.setOnClickListener { if (isSystemDns()) { io { @@ -387,6 +403,37 @@ class DnsSettingsFragment : } } } + + b.dvBypassDnsBlockSwitch.setOnCheckedChangeListener { _, isChecked -> + persistentState.bypassBlockInDns = isChecked + } + + b.dvBypassDnsBlockRl.setOnClickListener { + b.dvBypassDnsBlockSwitch.isChecked = !b.dvBypassDnsBlockSwitch.isChecked + } + + b.dcSplitDnsSwitch.setOnCheckedChangeListener { _, isChecked -> + persistentState.splitDns = isChecked + } + + b.dcSplitDnsRl.setOnClickListener { + b.dcSplitDnsSwitch.isChecked = !b.dcSplitDnsSwitch.isChecked + } + + b.networkDnsInfo.setOnClickListener { + io { + val sysDns = VpnController.getSystemDns() + uiCtx { showSystemDnsDialog(sysDns) } + } + } + + b.dcUndelegatedDomainsRl.setOnClickListener { + b.dcUndelegatedDomainsSwitch.isChecked = !b.dcUndelegatedDomainsSwitch.isChecked + } + + b.dcUndelegatedDomainsSwitch.setOnCheckedChangeListener { _, isChecked -> + persistentState.useSystemDnsForUndelegatedDomains = isChecked + } } private fun showSystemDnsDialog(dns: String) { diff --git a/app/src/main/res/layout/fragment_dns_configure.xml b/app/src/main/res/layout/fragment_dns_configure.xml index 36a726cc8..3265131b9 100644 --- a/app/src/main/res/layout/fragment_dns_configure.xml +++ b/app/src/main/res/layout/fragment_dns_configure.xml @@ -60,120 +60,118 @@ android:gravity="center" android:orientation="vertical"> - + - - - + android:layout_gravity="center" + android:layout_marginStart="10dp" + android:layout_marginEnd="10dp" + android:layout_marginBottom="10dp"> - - - - - + android:src="@drawable/ic_info_white_16" /> - + - - - + android:drawableEnd="@drawable/ic_right_arrow_white" + android:drawablePadding="10dp" + android:enabled="true" + android:minHeight="48dp" + android:padding="10dp" + android:text="@string/dc_custom_dns_radio" /> - - + + + + + + @@ -272,6 +270,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + android:src="@drawable/bs_dns_home_screen" /> + + android:src="@drawable/bs_dns_home_screen" /> + - + + + + + + + + + + + + + + + From 8698b532fd30fe42c8e959d9f0b7a43ac0c0f11d Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 28 Sep 2024 22:01:25 +0530 Subject: [PATCH 155/188] ui: add new advanced configuration option in settings screen --- .../bravedns/ui/fragment/ConfigureFragment.kt | 15 ++++- .../main/res/layout/fragment_configure.xml | 58 +++++++++++++++++-- 2 files changed, 67 insertions(+), 6 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/ConfigureFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/ConfigureFragment.kt index 5592dcd34..bc4186302 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/ConfigureFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/ConfigureFragment.kt @@ -23,6 +23,7 @@ import by.kirich1409.viewbindingdelegate.viewBinding import com.celzero.bravedns.R import com.celzero.bravedns.databinding.FragmentConfigureBinding import com.celzero.bravedns.ui.activity.AppListActivity +import com.celzero.bravedns.ui.activity.AdvancedSettingActivity import com.celzero.bravedns.ui.activity.DnsDetailActivity import com.celzero.bravedns.ui.activity.FirewallActivity import com.celzero.bravedns.ui.activity.MiscSettingsActivity @@ -41,18 +42,23 @@ class ConfigureFragment : Fragment(R.layout.fragment_configure) { PROXY, VPN, OTHERS, - LOGS + LOGS, + ADVANCED } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) initView() + setupClickListeners() } private fun initView() { b.fsNetworkTv.text = getString(R.string.lbl_network).replaceFirstChar(Char::titlecase) b.fsLogsTv.text = getString(R.string.lbl_logs).replaceFirstChar(Char::titlecase) + b.fsDevOptTv.text = getString(R.string.lbl_advanced).replaceFirstChar(Char::titlecase) + } + private fun setupClickListeners() { b.fsAppsCard.setOnClickListener { // open apps configuration startActivity(ScreenType.APPS) @@ -87,6 +93,11 @@ class ConfigureFragment : Fragment(R.layout.fragment_configure) { // open logs configuration startActivity(ScreenType.LOGS) } + + b.fsDevOptCard.setOnClickListener { + // open developer options configuration + startActivity(ScreenType.ADVANCED) + } } private fun startActivity(type: ScreenType) { @@ -99,8 +110,8 @@ class ConfigureFragment : Fragment(R.layout.fragment_configure) { ScreenType.VPN -> Intent(requireContext(), TunnelSettingsActivity::class.java) ScreenType.OTHERS -> Intent(requireContext(), MiscSettingsActivity::class.java) ScreenType.LOGS -> Intent(requireContext(), NetworkLogsActivity::class.java) + ScreenType.ADVANCED -> Intent(requireContext(), AdvancedSettingActivity::class.java) } - intent.flags = Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED startActivity(intent) } } diff --git a/app/src/main/res/layout/fragment_configure.xml b/app/src/main/res/layout/fragment_configure.xml index 8f0d47756..00e51684a 100644 --- a/app/src/main/res/layout/fragment_configure.xml +++ b/app/src/main/res/layout/fragment_configure.xml @@ -5,7 +5,8 @@ android:layout_height="match_parent" android:background="?attr/background" android:fillViewport="true" - android:orientation="vertical"> + android:orientation="vertical" + android:paddingBottom="60dp"> - - - + + + + + + + + + + + + Date: Sat, 28 Sep 2024 22:08:20 +0530 Subject: [PATCH 156/188] ui: minor corrections in custom domain/ip rules screen --- app/src/full/res/layout/list_item_custom_all_domain.xml | 1 + app/src/full/res/layout/list_item_custom_all_ip.xml | 1 + app/src/full/res/layout/list_item_firewall_app.xml | 1 + 3 files changed, 3 insertions(+) diff --git a/app/src/full/res/layout/list_item_custom_all_domain.xml b/app/src/full/res/layout/list_item_custom_all_domain.xml index ce34d1503..fcae3488f 100644 --- a/app/src/full/res/layout/list_item_custom_all_domain.xml +++ b/app/src/full/res/layout/list_item_custom_all_domain.xml @@ -17,6 +17,7 @@ android:layout_height="wrap_content" android:gravity="center_vertical" android:orientation="horizontal" + android:layout_marginTop="10dp" android:padding="10dp"> From 00aab7a25db1167b1632d717bb0145980f552571 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Wed, 2 Oct 2024 19:28:42 +0530 Subject: [PATCH 157/188] fix: rmv incorrect dir deletion from download mgr --- .../bravedns/download/BlocklistDownloadHelper.kt | 8 -------- .../bravedns/download/FileHandleWorker.kt | 16 ++++++++++++---- .../bravedns/service/RethinkBlocklistManager.kt | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/download/BlocklistDownloadHelper.kt b/app/src/full/java/com/celzero/bravedns/download/BlocklistDownloadHelper.kt index 14836f859..12888c4c0 100644 --- a/app/src/full/java/com/celzero/bravedns/download/BlocklistDownloadHelper.kt +++ b/app/src/full/java/com/celzero/bravedns/download/BlocklistDownloadHelper.kt @@ -106,14 +106,6 @@ class BlocklistDownloadHelper { } } - fun deleteFromCanonicalPath(context: Context) { - val canonicalPath = - File( - blocklistCanonicalPath(context, Constants.LOCAL_BLOCKLIST_DOWNLOAD_FOLDER_NAME) - ) - deleteRecursive(canonicalPath) - } - fun getExternalFilePath(context: Context, timestamp: String): String { return context.getExternalFilesDir(null).toString() + Constants.ONDEVICE_BLOCKLIST_DOWNLOAD_PATH + diff --git a/app/src/full/java/com/celzero/bravedns/download/FileHandleWorker.kt b/app/src/full/java/com/celzero/bravedns/download/FileHandleWorker.kt index 60fca49a9..919c8cf11 100644 --- a/app/src/full/java/com/celzero/bravedns/download/FileHandleWorker.kt +++ b/app/src/full/java/com/celzero/bravedns/download/FileHandleWorker.kt @@ -21,6 +21,7 @@ import android.content.Context import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import androidx.work.workDataOf +import com.celzero.bravedns.download.BlocklistDownloadHelper.Companion.deleteBlocklistResidue import com.celzero.bravedns.download.BlocklistDownloadHelper.Companion.deleteOldFiles import com.celzero.bravedns.service.PersistentState import com.celzero.bravedns.service.RethinkBlocklistManager @@ -87,7 +88,6 @@ class FileHandleWorker(val context: Context, workerParameters: WorkerParameters) return false } - BlocklistDownloadHelper.deleteFromCanonicalPath(context) val dir = File(BlocklistDownloadHelper.getExternalFilePath(context, timestamp.toString())) if (!dir.isDirectory) { @@ -139,10 +139,18 @@ class FileHandleWorker(val context: Context, workerParameters: WorkerParameters) val result = updateTagsToDb(timestamp) updatePersistenceOnCopySuccess(timestamp) + // delete the old files in the external directory (downloaded by the download manager) deleteOldFiles(context, timestamp, RethinkBlocklistManager.DownloadType.LOCAL) + // delete the residue files in the app data directory (local_blocklist) + deleteBlocklistResidue( + context, + Constants.REMOTE_BLOCKLIST_DOWNLOAD_FOLDER_NAME, + timestamp + ) + Logger.i(LOG_TAG_DOWNLOAD, "FileHandleWorker, copyFiles success? $result") return true } catch (e: Exception) { - Logger.e(LOG_TAG_DOWNLOAD, "AppDownloadManager Copy exception: ${e.message}", e) + Logger.e(LOG_TAG_DOWNLOAD, "FileHandleWorker Copy exception: ${e.message}", e) } return false } @@ -188,10 +196,10 @@ class FileHandleWorker(val context: Context, workerParameters: WorkerParameters) "tdmd5: $tdmd5, rdmd5: $rdmd5, remotetd: $remoteTdmd5, remoterd: $remoteRdmd5" ) val isDownloadValid = tdmd5 == remoteTdmd5 && rdmd5 == remoteRdmd5 - Logger.i(LOG_TAG_DOWNLOAD, "AppDownloadManager, isDownloadValid? $isDownloadValid") + Logger.i(LOG_TAG_DOWNLOAD, "FileHandleWorker, isDownloadValid? $isDownloadValid") return isDownloadValid } catch (e: Exception) { - Logger.e(LOG_TAG_DOWNLOAD, "AppDownloadManager, isDownloadValid err: ${e.message}", e) + Logger.e(LOG_TAG_DOWNLOAD, "FileHandleWorker, isDownloadValid err: ${e.message}", e) } return false } diff --git a/app/src/main/java/com/celzero/bravedns/service/RethinkBlocklistManager.kt b/app/src/main/java/com/celzero/bravedns/service/RethinkBlocklistManager.kt index 5b17b1574..cc851229c 100644 --- a/app/src/main/java/com/celzero/bravedns/service/RethinkBlocklistManager.kt +++ b/app/src/main/java/com/celzero/bravedns/service/RethinkBlocklistManager.kt @@ -159,7 +159,7 @@ object RethinkBlocklistManager : KoinComponent { packsBlocklistMapping.put(PacksMappingKey(s, 0), l.value) return@forEachIndexed } - val level = l.level?.elementAt(index) ?: 2 + val level = l.level?.getOrNull(index) ?: 2 packsBlocklistMapping.put(PacksMappingKey(s, level), l.value) } } @@ -239,7 +239,7 @@ object RethinkBlocklistManager : KoinComponent { return@forEachIndexed } // if the level is empty, then set the level to 2 (assume highest) #756 - val level = r.level?.elementAt(index) ?: 2 + val level = r.level?.getOrNull(index) ?: 2 packsBlocklistMapping.put(PacksMappingKey(s, level), r.value) } } From 84baf59adee2855018b5733b330dd8af7ce8ba82 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Wed, 2 Oct 2024 19:29:27 +0530 Subject: [PATCH 158/188] ui: new conf for slowdown only in debug mode --- .../ui/activity/AdvancedSettingActivity.kt | 14 +++++ .../res/layout/activity_advanced_setting.xml | 54 +++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/AdvancedSettingActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/AdvancedSettingActivity.kt index db5afb51a..d353657c3 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/AdvancedSettingActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/AdvancedSettingActivity.kt @@ -99,6 +99,12 @@ class AdvancedSettingActivity : AppCompatActivity(R.layout.activity_advanced_set } else { b.dvTcpKeepAliveRl.visibility = View.GONE } + if (DEBUG) { + b.settingsActivitySlowdownRl.visibility = View.VISIBLE + b.settingsActivitySlowdownSwitch.isChecked = persistentState.slowdownMode + } else { + b.settingsActivitySlowdownRl.visibility = View.GONE + } displayPcapUi() } @@ -154,6 +160,14 @@ class AdvancedSettingActivity : AppCompatActivity(R.layout.activity_advanced_set } b.dvTcpKeepAliveRl.setOnClickListener { b.dvTcpKeepAliveSwitch.isChecked = !b.dvTcpKeepAliveSwitch.isChecked } + + b.settingsActivitySlowdownRl.setOnClickListener { + b.settingsActivitySlowdownSwitch.isChecked = !b.settingsActivitySlowdownSwitch.isChecked + } + + b.settingsActivitySlowdownSwitch.setOnCheckedChangeListener { _, isChecked -> + persistentState.slowdownMode = isChecked + } } private fun displayPcapUi() { diff --git a/app/src/full/res/layout/activity_advanced_setting.xml b/app/src/full/res/layout/activity_advanced_setting.xml index dc9cbc314..f2bf86e1c 100644 --- a/app/src/full/res/layout/activity_advanced_setting.xml +++ b/app/src/full/res/layout/activity_advanced_setting.xml @@ -525,6 +525,60 @@ android:padding="10dp" /> + + + + + + + + + + + + + + From 9d0e60d508b69f552f31c743a3dfe6c4068f7a35 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Wed, 2 Oct 2024 19:30:08 +0530 Subject: [PATCH 159/188] ui: show country name along with flag in country-wise app list --- .../celzero/bravedns/ui/activity/DomainConnectionsActivity.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/DomainConnectionsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/DomainConnectionsActivity.kt index b51a04573..1dd389186 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/DomainConnectionsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/DomainConnectionsActivity.kt @@ -29,6 +29,7 @@ import com.celzero.bravedns.databinding.ActivityDomainConnectionsBinding import com.celzero.bravedns.service.PersistentState import com.celzero.bravedns.util.CustomLinearLayoutManager import com.celzero.bravedns.util.Themes.Companion.getCurrentTheme +import com.celzero.bravedns.util.UIUtils.getCountryNameFromFlag import com.celzero.bravedns.viewmodel.DomainConnectionsViewModel import com.celzero.bravedns.viewmodel.SummaryStatisticsViewModel import org.koin.android.ext.android.inject @@ -72,9 +73,8 @@ class DomainConnectionsActivity : AppCompatActivity(R.layout.activity_domain_con val flag = intent.getStringExtra(INTENT_FLAG) ?: "" viewModel.setFlag(flag) b.dcFlag.visibility = View.VISIBLE - b.dcFlag.text = flag + b.dcFlag.text = getString(R.string.two_argument_space, flag, getCountryNameFromFlag(flag)) b.dcTitle.visibility = View.GONE - Logger.d(LOG_TAG_UI, "Flag: $flag, Type: $type") } val tc = intent.getIntExtra(INTENT_TIME_CATEGORY, 0) val timeCategory = From 5511ec5f06ede5c49355d4fd0dcbb4b1ade332a5 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Wed, 2 Oct 2024 19:32:16 +0530 Subject: [PATCH 160/188] ui: access to wg logs from wg screen --- .../ui/activity/NetworkLogsActivity.kt | 12 +++- .../ui/fragment/ConnectionTrackerFragment.kt | 61 ++++++++++++++----- 2 files changed, 55 insertions(+), 18 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/NetworkLogsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/NetworkLogsActivity.kt index 323df4a7e..a6629f303 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/NetworkLogsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/NetworkLogsActivity.kt @@ -46,6 +46,8 @@ class NetworkLogsActivity : AppCompatActivity(R.layout.activity_network_logs) { // to handle search navigation from universal firewall, to show only the search results // of the selected universal rule, show only network logs tab private var isUnivNavigated = false + // to handle the wireguard connections + private var isWireGuardLogs = false private val persistentState by inject() private val appConfig by inject() @@ -55,6 +57,10 @@ class NetworkLogsActivity : AppCompatActivity(R.layout.activity_network_logs) { DNS_LOGS(1), RETHINK_LOGS(2) } + + companion object { + const val RULES_SEARCH_ID_WIREGUARD = "W:" + } override fun onCreate(savedInstanceState: Bundle?) { setTheme(getCurrentTheme(isDarkThemeOn(), persistentState.theme)) @@ -63,6 +69,8 @@ class NetworkLogsActivity : AppCompatActivity(R.layout.activity_network_logs) { searchParam = intent.getStringExtra(Constants.SEARCH_QUERY) ?: "" if (searchParam.contains(RULES_SEARCH_ID)) { isUnivNavigated = true + } else if(searchParam.contains(RULES_SEARCH_ID_WIREGUARD)) { + isWireGuardLogs = true } init() } @@ -106,7 +114,7 @@ class NetworkLogsActivity : AppCompatActivity(R.layout.activity_network_logs) { } private fun getCount(): Int { - if (isUnivNavigated) { + if (isUnivNavigated || isWireGuardLogs) { return 1 } @@ -122,7 +130,7 @@ class NetworkLogsActivity : AppCompatActivity(R.layout.activity_network_logs) { } private fun getFragment(position: Int): Fragment { - if (isUnivNavigated) { + if (isUnivNavigated || isWireGuardLogs) { return ConnectionTrackerFragment.newInstance(searchParam) } return when (position) { diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/ConnectionTrackerFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/ConnectionTrackerFragment.kt index 6bdba64a0..f19337eb6 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/ConnectionTrackerFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/ConnectionTrackerFragment.kt @@ -34,6 +34,7 @@ import com.celzero.bravedns.database.ConnectionTrackerRepository import com.celzero.bravedns.databinding.FragmentConnectionTrackerBinding import com.celzero.bravedns.service.FirewallRuleset import com.celzero.bravedns.service.PersistentState +import com.celzero.bravedns.ui.activity.NetworkLogsActivity import com.celzero.bravedns.ui.activity.UniversalFirewallSettingsActivity import com.celzero.bravedns.util.Constants import com.celzero.bravedns.util.UIUtils.formatToRelativeTime @@ -79,12 +80,20 @@ class ConnectionTrackerFragment : initView() if (arguments != null) { val query = arguments?.getString(Constants.SEARCH_QUERY) ?: return - if (query.contains(UniversalFirewallSettingsActivity.RULES_SEARCH_ID)) { + val containsUniv = query.contains(UniversalFirewallSettingsActivity.RULES_SEARCH_ID) + val containsWireGuard = query.contains(NetworkLogsActivity.RULES_SEARCH_ID_WIREGUARD) + if (containsUniv) { val rule = query.split(UniversalFirewallSettingsActivity.RULES_SEARCH_ID)[1] filterCategories.add(rule) filterType = TopLevelFilter.BLOCKED viewModel.setFilter(filterQuery, filterCategories, filterType) hideSearchLayout() + } else if (containsWireGuard) { + val rule = query.split(NetworkLogsActivity.RULES_SEARCH_ID_WIREGUARD)[1] + filterQuery = rule + filterType = TopLevelFilter.ALL + viewModel.setFilter(filterQuery, filterCategories, filterType) + hideSearchLayout() } else { b.connectionSearch.setQuery(query, true) } @@ -102,21 +111,7 @@ class ConnectionTrackerFragment : b.connectionListLogsDisabledTv.visibility = View.GONE b.connectionCardViewTop.visibility = View.VISIBLE - b.recyclerConnection.setHasFixedSize(true) - layoutManager = LinearLayoutManager(requireContext()) - b.recyclerConnection.layoutManager = layoutManager - val recyclerAdapter = ConnectionTrackerAdapter(requireContext()) - recyclerAdapter.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY - viewLifecycleOwner.lifecycleScope.launch { - repeatOnLifecycle(Lifecycle.State.STARTED) { - viewModel.connectionTrackerList.observe(viewLifecycleOwner) { it -> - recyclerAdapter.submitData(lifecycle, it) - } - } - } - b.recyclerConnection.adapter = recyclerAdapter - - setupRecyclerScrollListener() + setupRecyclerView() b.connectionSearch.setOnQueryTextListener(this) b.connectionSearch.setOnClickListener { @@ -134,6 +129,40 @@ class ConnectionTrackerFragment : remakeChildFilterChipsUi(FirewallRuleset.getBlockedRules()) } + private fun setupRecyclerView() { + b.recyclerConnection.setHasFixedSize(true) + layoutManager = LinearLayoutManager(requireContext()) + b.recyclerConnection.layoutManager = layoutManager + val recyclerAdapter = ConnectionTrackerAdapter(requireContext()) + recyclerAdapter.stateRestorationPolicy = + RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY + viewLifecycleOwner.lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.connectionTrackerList.observe(viewLifecycleOwner) { pagingData -> + recyclerAdapter.submitData(lifecycle, pagingData) + } + } + } + recyclerAdapter.addLoadStateListener { + if (it.append.endOfPaginationReached) { + if (recyclerAdapter.itemCount < 1) { + b.connectionListLogsDisabledTv.text = getString(R.string.ada_ip_no_connection) + b.connectionListLogsDisabledTv.visibility = View.VISIBLE + b.connectionCardViewTop.visibility = View.GONE + } else { + b.connectionListLogsDisabledTv.visibility = View.GONE + b.connectionCardViewTop.visibility = View.VISIBLE + } + } else { + b.connectionListLogsDisabledTv.visibility = View.GONE + b.connectionCardViewTop.visibility = View.VISIBLE + } + } + b.recyclerConnection.adapter = recyclerAdapter + + setupRecyclerScrollListener() + } + private fun hideSearchLayout() { b.connectionCardViewTop.visibility = View.GONE } From 36bb0d2956c99e1e9305220eae0d53d4528df42d Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Wed, 2 Oct 2024 19:32:41 +0530 Subject: [PATCH 161/188] manifest.xml: new activities included as part of v055o --- app/src/full/AndroidManifest.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/full/AndroidManifest.xml b/app/src/full/AndroidManifest.xml index 5b4a68bb0..3dc2339ae 100644 --- a/app/src/full/AndroidManifest.xml +++ b/app/src/full/AndroidManifest.xml @@ -10,6 +10,9 @@ android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" tools:ignore="ScopedStorage" /> + + + + Date: Wed, 2 Oct 2024 19:35:09 +0530 Subject: [PATCH 162/188] minor ui and other improvements as part of v055o --- .../bravedns/scheduler/EnhancedBugReport.kt | 2 +- .../celzero/bravedns/ui/HomeScreenActivity.kt | 82 +++++++++++++++---- .../bravedns/ui/activity/WgMainActivity.kt | 5 +- .../HomeScreenSettingBottomSheet.kt | 3 +- .../bravedns/ui/fragment/ConfigureFragment.kt | 4 +- .../viewmodel/AppConnectionsViewModel.kt | 5 +- .../bravedns/database/ConnectionTracker.kt | 3 +- .../celzero/bravedns/database/LogDatabase.kt | 17 ++-- .../bravedns/service/EncryptedFileManager.kt | 2 +- 9 files changed, 89 insertions(+), 34 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/scheduler/EnhancedBugReport.kt b/app/src/full/java/com/celzero/bravedns/scheduler/EnhancedBugReport.kt index 5635a0cb5..c4f63199b 100644 --- a/app/src/full/java/com/celzero/bravedns/scheduler/EnhancedBugReport.kt +++ b/app/src/full/java/com/celzero/bravedns/scheduler/EnhancedBugReport.kt @@ -85,7 +85,7 @@ object EnhancedBugReport { } } - private fun getFileToWrite(context: Context): File? { + fun getFileToWrite(context: Context): File? { val file = getTombstoneFile(context) Log.d(LOG_TAG_BUG_REPORT, "file to write logs: ${file?.name}") return file diff --git a/app/src/full/java/com/celzero/bravedns/ui/HomeScreenActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/HomeScreenActivity.kt index 7521fbf99..d66afbbc8 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/HomeScreenActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/HomeScreenActivity.kt @@ -29,7 +29,6 @@ import android.content.res.Configuration import android.content.res.Configuration.UI_MODE_NIGHT_YES import android.net.Uri import android.os.Bundle -import android.os.PersistableBundle import android.os.SystemClock import android.view.View import android.widget.Toast @@ -37,6 +36,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.biometric.BiometricManager import androidx.biometric.BiometricPrompt import androidx.core.content.ContextCompat +import androidx.fragment.app.FragmentActivity import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.setupWithNavController @@ -63,6 +63,7 @@ import com.celzero.bravedns.service.BraveVPNService import com.celzero.bravedns.service.PersistentState import com.celzero.bravedns.service.RethinkBlocklistManager import com.celzero.bravedns.service.VpnController +import com.celzero.bravedns.ui.activity.MiscSettingsActivity import com.celzero.bravedns.ui.activity.PauseActivity import com.celzero.bravedns.ui.activity.WelcomeActivity import com.celzero.bravedns.util.Constants @@ -77,13 +78,15 @@ import com.celzero.bravedns.util.Utilities.showToastUiCentered import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.snackbar.Snackbar -import java.util.Calendar -import java.util.concurrent.Executor -import java.util.concurrent.TimeUnit import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.koin.android.ext.android.get import org.koin.android.ext.android.inject +import java.util.Calendar +import java.util.concurrent.Executor +import java.util.concurrent.TimeUnit +import kotlin.math.abs + class HomeScreenActivity : AppCompatActivity(R.layout.activity_home_screen) { private val b by viewBinding(ActivityHomeScreenBinding::bind) @@ -98,9 +101,7 @@ class HomeScreenActivity : AppCompatActivity(R.layout.activity_home_screen) { private lateinit var biometricPrompt: BiometricPrompt private lateinit var promptInfo: BiometricPrompt.PromptInfo - companion object { - private const val BIOMETRIC_TIMEOUT_MINUTES = 15L - } + private var isActivityStarted = false // TODO - #324 - Usage of isDarkTheme() in all activities. private fun Context.isDarkThemeOn(): Boolean { @@ -112,19 +113,13 @@ class HomeScreenActivity : AppCompatActivity(R.layout.activity_home_screen) { setTheme(getCurrentTheme(isDarkThemeOn(), persistentState.theme)) super.onCreate(savedInstanceState) - val isAppRunningOnTv = isAppRunningOnTv() - // do not launch on board activity when app is running on TV - if (persistentState.firstTimeLaunch && !isAppRunningOnTv) { + if (persistentState.firstTimeLaunch && !isAppRunningOnTv()) { launchOnboardActivity() return } updateNewVersion() - if (persistentState.biometricAuth && !isAppRunningOnTv) { - biometricPrompt() - } - setupNavigationItemSelectedListener() // handle intent receiver for backup/restore @@ -133,6 +128,37 @@ class HomeScreenActivity : AppCompatActivity(R.layout.activity_home_screen) { initUpdateCheck() observeAppState() + isActivityStarted = false + } + + override fun onDestroy() { + super.onDestroy() + } + + override fun onPause() { + super.onPause() + } + + override fun onResume() { + super.onResume() + Logger.v(LOG_TAG_UI, "isActivityStarted: $isActivityStarted, intent: $intent, action: ${intent?.action}") + if (!isActivityStarted) { + isActivityStarted = true + Logger.vv(LOG_TAG_UI, "HomeScreenActivity is resumed") + if (intent != null) { // intent is not null means the activity is started from intent + Logger.vv(LOG_TAG_UI, "HomeScreenActivity is started from intent") + Logger.vv(LOG_TAG_UI, "isBiometricEnabled: ${isBiometricEnabled()}, isAppRunningOnTv: ${isAppRunningOnTv()}") + if (isBiometricEnabled() && !isAppRunningOnTv()) { + biometricPrompt() + } + } + } + } + + private fun isBiometricEnabled(): Boolean { + val type = MiscSettingsActivity.BioMetricType.fromValue(persistentState.biometricAuthType) + // use the biometricAuth flag for backward compatibility with older versions + return persistentState.biometricAuth || type.enabled() } // check if app running on TV @@ -147,9 +173,18 @@ class HomeScreenActivity : AppCompatActivity(R.layout.activity_home_screen) { private fun biometricPrompt() { // if the biometric authentication is already done in the last 15 minutes, then skip - // fixme - #324 - move the 15 minutes to a configurable value - val timeSinceLastAuth = SystemClock.elapsedRealtime() - persistentState.biometricAuthTime - if (timeSinceLastAuth < TimeUnit.MINUTES.toMillis(BIOMETRIC_TIMEOUT_MINUTES)) { + val minutes = MiscSettingsActivity.BioMetricType.fromValue(persistentState.biometricAuthType).mins + + val timeoutMinutes = if (minutes == -1L) { // this is for backward compatibility with older versions + MiscSettingsActivity.BioMetricType.FIFTEEN_MIN.mins + } else { + minutes + } + + Logger.d(LOG_TAG_UI, "Biometric timeout: $timeoutMinutes, biometricAuthTime: ${persistentState.biometricAuthTime}") + val timeSinceLastAuth = abs(SystemClock.elapsedRealtime() - persistentState.biometricAuthTime) + if (timeSinceLastAuth < TimeUnit.MINUTES.toMillis(timeoutMinutes)) { + Logger.i(LOG_TAG_UI, "Biometric auth skipped, time since last auth: $timeSinceLastAuth") return } @@ -212,7 +247,7 @@ class HomeScreenActivity : AppCompatActivity(R.layout.activity_home_screen) { super.onAuthenticationSucceeded(result) // biometricPromptRetryCount = 1 persistentState.biometricAuthTime = SystemClock.elapsedRealtime() - Logger.i(LOG_TAG_UI, "Biometric success @ ${System.currentTimeMillis()}") + Logger.i(LOG_TAG_UI, "Biometric success @ ${SystemClock.elapsedRealtime()}") } override fun onAuthenticationFailed() { @@ -365,6 +400,12 @@ class HomeScreenActivity : AppCompatActivity(R.layout.activity_home_screen) { } private fun removeThisMethod() { + // set allowBypass to false for all versions, overriding the user's preference. + // the default was true for Play Store and website versions, and false for F-Droid. + // when allowBypass is true, some OEMs bypass the VPN service, causing connections + // to fail due to the "Block connections without VPN" option. + persistentState.allowBypass = false + // change the persistent state for defaultDnsUrl, if its google.com (only for v055d) // fixme: remove this post v054. // this is to fix the default dns url, as the default dns url is changed from @@ -379,6 +420,10 @@ class HomeScreenActivity : AppCompatActivity(R.layout.activity_home_screen) { persistentState.biometricAuthTime = SystemClock.elapsedRealtime() // set the rethink app in firewall mode as allowed by default io { appInfoDb.resetRethinkAppFirewallMode() } + // if biometric auth is enabled, then set the biometric auth type to 3 (15 minutes) + if (persistentState.biometricAuth) { + persistentState.biometricAuthType = MiscSettingsActivity.BioMetricType.FIFTEEN_MIN.action + } } // fixme: find a cleaner way to implement this, move this to some other place @@ -665,6 +710,7 @@ class HomeScreenActivity : AppCompatActivity(R.layout.activity_home_screen) { } catch (e: IllegalArgumentException) { Logger.w(LOG_TAG_DOWNLOAD, "Unregister receiver exception") } + Logger.d(LOG_TAG_UI, "HomeScreenActivity is stopped") } private fun setupNavigationItemSelectedListener() { diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/WgMainActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/WgMainActivity.kt index bf83bd935..df5f10dd2 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/WgMainActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/WgMainActivity.kt @@ -25,7 +25,6 @@ import android.os.Bundle import android.view.View import android.widget.Toast import androidx.activity.OnBackPressedCallback -import androidx.activity.addCallback import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AppCompatActivity import androidx.lifecycle.lifecycleScope @@ -213,7 +212,7 @@ class WgMainActivity : val layoutManager = LinearLayoutManager(this) b.wgGeneralInterfaceList.layoutManager = layoutManager - wgConfigAdapter = WgConfigAdapter(this, this, persistentState) + wgConfigAdapter = WgConfigAdapter(this, this, persistentState.splitDns) wgConfigViewModel.interfaces.observe(this) { wgConfigAdapter?.submitData(lifecycle, it) } b.wgGeneralInterfaceList.adapter = wgConfigAdapter } @@ -222,7 +221,7 @@ class WgMainActivity : val layoutManager = LinearLayoutManager(this) b.oneWgInterfaceList.layoutManager = layoutManager - oneWgConfigAdapter = OneWgConfigAdapter(this, this, persistentState) + oneWgConfigAdapter = OneWgConfigAdapter(this, this) wgConfigViewModel.interfaces.observe(this) { oneWgConfigAdapter?.submitData(lifecycle, it) } b.oneWgInterfaceList.adapter = oneWgConfigAdapter } diff --git a/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/HomeScreenSettingBottomSheet.kt b/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/HomeScreenSettingBottomSheet.kt index a72b43632..a69f101d4 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/HomeScreenSettingBottomSheet.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/bottomsheet/HomeScreenSettingBottomSheet.kt @@ -42,6 +42,7 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.koin.android.ext.android.inject +import kotlin.math.abs class HomeScreenSettingBottomSheet : BottomSheetDialogFragment() { private var _binding: BottomSheetHomeScreenBinding? = null @@ -89,7 +90,7 @@ class HomeScreenSettingBottomSheet : BottomSheetDialogFragment() { Logger.d(LOG_TAG_VPN, "Home screen bottom sheet selectedIndex: $selectedIndex") if (DEBUG) { - val timeSinceLastAuth = SystemClock.elapsedRealtime() - persistentState.biometricAuthTime + val timeSinceLastAuth = abs(SystemClock.elapsedRealtime() - persistentState.biometricAuthTime) b.bsHomeScreenAppLockTime.visibility = View.VISIBLE b.bsHomeScreenAppLockTime.text = java.util.concurrent.TimeUnit.MILLISECONDS.toMinutes(timeSinceLastAuth).toString() + " minutes" Logger.d(LOG_TAG_VPN, "last auth time: ${persistentState.biometricAuthTime}") diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/ConfigureFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/ConfigureFragment.kt index bc4186302..c4c26bb02 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/ConfigureFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/ConfigureFragment.kt @@ -55,7 +55,7 @@ class ConfigureFragment : Fragment(R.layout.fragment_configure) { private fun initView() { b.fsNetworkTv.text = getString(R.string.lbl_network).replaceFirstChar(Char::titlecase) b.fsLogsTv.text = getString(R.string.lbl_logs).replaceFirstChar(Char::titlecase) - b.fsDevOptTv.text = getString(R.string.lbl_advanced).replaceFirstChar(Char::titlecase) + b.fsAdvancedTv.text = getString(R.string.lbl_advanced).replaceFirstChar(Char::titlecase) } private fun setupClickListeners() { @@ -94,7 +94,7 @@ class ConfigureFragment : Fragment(R.layout.fragment_configure) { startActivity(ScreenType.LOGS) } - b.fsDevOptCard.setOnClickListener { + b.fsAdvancedCard.setOnClickListener { // open developer options configuration startActivity(ScreenType.ADVANCED) } diff --git a/app/src/full/java/com/celzero/bravedns/viewmodel/AppConnectionsViewModel.kt b/app/src/full/java/com/celzero/bravedns/viewmodel/AppConnectionsViewModel.kt index 4435d37d5..7c3568617 100644 --- a/app/src/full/java/com/celzero/bravedns/viewmodel/AppConnectionsViewModel.kt +++ b/app/src/full/java/com/celzero/bravedns/viewmodel/AppConnectionsViewModel.kt @@ -15,6 +15,7 @@ */ package com.celzero.bravedns.viewmodel +import Logger import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -100,7 +101,9 @@ class AppConnectionsViewModel(private val nwlogDao: ConnectionTrackerDAO, privat } val appIpLogs = ipFilter.switchMap { input -> fetchIpLogs(uid, input) } - val appDomainLogs = domainFilter.switchMap { input -> fetchAppDomainLogs(uid, input) } + val appDomainLogs = domainFilter.switchMap { + input -> fetchAppDomainLogs(uid, input) + } val rinrIpLogs = ipFilter.switchMap { input -> fetchRinrIpLogs(input) } val rinrDomainLogs = domainFilter.switchMap { input -> fetchRinrDomainLogs(input) } diff --git a/app/src/main/java/com/celzero/bravedns/database/ConnectionTracker.kt b/app/src/main/java/com/celzero/bravedns/database/ConnectionTracker.kt index 6820024af..8aaa374bc 100644 --- a/app/src/main/java/com/celzero/bravedns/database/ConnectionTracker.kt +++ b/app/src/main/java/com/celzero/bravedns/database/ConnectionTracker.kt @@ -29,7 +29,8 @@ import com.celzero.bravedns.util.Constants.Companion.INIT_TIME_MS Index(value = arrayOf("dnsQuery"), unique = false), Index(value = arrayOf("blockedByRule"), unique = false), Index(value = arrayOf("isBlocked", "timeStamp"), unique = false), - Index(value = arrayOf("connId"), unique = false) + Index(value = arrayOf("connId"), unique = false), + Index(value = arrayOf("proxyDetails"), unique = false) ] ) class ConnectionTracker { diff --git a/app/src/main/java/com/celzero/bravedns/database/LogDatabase.kt b/app/src/main/java/com/celzero/bravedns/database/LogDatabase.kt index 2427781f2..129395484 100644 --- a/app/src/main/java/com/celzero/bravedns/database/LogDatabase.kt +++ b/app/src/main/java/com/celzero/bravedns/database/LogDatabase.kt @@ -17,7 +17,6 @@ package com.celzero.bravedns.database import Logger import android.content.Context -import android.content.pm.PackageInfo import android.database.Cursor import android.database.sqlite.SQLiteException import androidx.room.Database @@ -27,12 +26,11 @@ import androidx.room.TypeConverters import androidx.room.migration.Migration import androidx.sqlite.db.SimpleSQLiteQuery import androidx.sqlite.db.SupportSQLiteDatabase -import androidx.work.impl.Migration_1_2 import com.celzero.bravedns.util.Utilities @Database( entities = [ConnectionTracker::class, DnsLog::class, RethinkLog::class], - version = 9, + version = 10, exportSchema = false ) @TypeConverters(Converters::class) @@ -71,6 +69,7 @@ abstract class LogDatabase : RoomDatabase() { .addMigrations(MIGRATION_6_7) .addMigrations(MIGRATION_7_8) .addMigrations(Migration_8_9) + .addMigrations(Migration_9_10) .fallbackToDestructiveMigration() // recreate the database if no migration is found .build() } @@ -277,9 +276,15 @@ abstract class LogDatabase : RoomDatabase() { } private val Migration_8_9: Migration = object : Migration(8, 9) { - override fun migrate(database: SupportSQLiteDatabase) { - database.execSQL("CREATE INDEX IF NOT EXISTS index_ConnectionTracker_connId ON ConnectionTracker(connId)") - database.execSQL("CREATE INDEX IF NOT EXISTS index_RethinkLog_connId ON RethinkLog(connId)") + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("CREATE INDEX IF NOT EXISTS index_ConnectionTracker_connId ON ConnectionTracker(connId)") + db.execSQL("CREATE INDEX IF NOT EXISTS index_RethinkLog_connId ON RethinkLog(connId)") + } + } + + private val Migration_9_10: Migration = object : Migration(9, 10) { + override fun migrate(db: SupportSQLiteDatabase) { + db.execSQL("CREATE INDEX IF NOT EXISTS index_ConnectionTracker_proxyDetails ON ConnectionTracker(proxyDetails)") } } } diff --git a/app/src/main/java/com/celzero/bravedns/service/EncryptedFileManager.kt b/app/src/main/java/com/celzero/bravedns/service/EncryptedFileManager.kt index 1a7bc184d..08235816c 100644 --- a/app/src/main/java/com/celzero/bravedns/service/EncryptedFileManager.kt +++ b/app/src/main/java/com/celzero/bravedns/service/EncryptedFileManager.kt @@ -134,7 +134,7 @@ object EncryptedFileManager { dir.mkdirs() } val fileToWrite = File(dir, fileName) - write(ctx, cfg, fileToWrite) + return write(ctx, cfg, fileToWrite) } catch (e: Exception) { Logger.e(Logger.LOG_TAG_PROXY, "Encrypted File Write: ${e.message}") } From 75f13f54942e662dee634eb56262323fd8ee4fd2 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Wed, 2 Oct 2024 19:45:53 +0530 Subject: [PATCH 163/188] wg: get config id based on uid, ignoring ip --- .../bravedns/service/WireguardManager.kt | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt b/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt index 3db9c4ea7..ab0db49be 100644 --- a/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt +++ b/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt @@ -484,6 +484,26 @@ object WireguardManager : KoinComponent { } } + suspend fun getConfigIdForApp(uid: Int): WgConfigFilesImmutable? { + val configId = ProxyManager.getProxyIdForApp(uid) + + val id = if (configId.isNotEmpty()) convertStringIdToId(configId) else INVALID_CONF_ID + val config = if (id == INVALID_CONF_ID) null else mappings.find { it.id == id } + if (config != null && (config.isActive || config.isLockdown)) { + Logger.d(LOG_TAG_PROXY, "app config mapping found for uid: $uid, $configId") + return config + } + + val catchAllConfig = getOptimalCatchAllConfigId() + return if (catchAllConfig == null) { + Logger.d(LOG_TAG_PROXY, "catch all config not found for uid: $uid") + null + } else { + Logger.d(LOG_TAG_PROXY, "catch all config found for uid: $uid, $catchAllConfig") + mappings.find { it.id == catchAllConfig } + } + } + private fun convertStringIdToId(id: String): Int { return try { val configId = id.substring(ProxyManager.ID_WG_BASE.length) @@ -1007,6 +1027,12 @@ object WireguardManager : KoinComponent { return VpnController.isSplitTunnelProxy(id, pair) } + fun invalidateCatchAllCache() { + if (catchAllAppConfigCache.value.isNotEmpty()) { + catchAllAppConfigCache.value = emptyMap() + } + } + private fun io(f: suspend () -> Unit) { CoroutineScope(Dispatchers.IO).launch { f() } } From f01e15802b55f1588689f298b08b834810b20d02 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Wed, 2 Oct 2024 19:46:15 +0530 Subject: [PATCH 164/188] bump android and firestack version for v055o --- app/build.gradle | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 9ffa74e8c..0ab44ab20 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -172,7 +172,7 @@ configurations { dependencies { def room_version = "2.6.1" - def paging_version = "3.3.0" + def paging_version = "3.3.2" implementation 'com.google.guava:guava:32.1.1-android' @@ -191,7 +191,7 @@ dependencies { fullImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3' // LiveData - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.2' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.8.6' implementation 'com.google.code.gson:gson:2.10.1' @@ -201,12 +201,12 @@ dependencies { implementation "androidx.room:room-paging:$room_version" fullImplementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' - fullImplementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.2' - fullImplementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.2' + fullImplementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.6' + fullImplementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.8.6' // Pagers Views implementation "androidx.paging:paging-runtime-ktx:$paging_version" - fullImplementation 'androidx.fragment:fragment-ktx:1.8.0' + fullImplementation 'androidx.fragment:fragment-ktx:1.8.3' implementation 'com.google.android.material:material:1.12.0' fullImplementation 'androidx.viewpager2:viewpager2:1.1.0' @@ -246,15 +246,14 @@ dependencies { fullImplementation 'com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.9' // from: https://jitpack.io/#celzero/firestack - download 'com.github.celzero:firestack:0751d67f29@aar' - websiteImplementation 'com.github.celzero:firestack:0751d67f29@aar' - fdroidImplementation 'com.github.celzero:firestack:0751d67f29@aar' + download 'com.github.celzero:firestack:a6ae729b45@aar' + websiteImplementation 'com.github.celzero:firestack:a6ae729b45@aar' + fdroidImplementation 'com.github.celzero:firestack:a6ae729b45@aar' // debug symbols - playImplementation 'com.github.celzero:firestack:0751d67f29:debug@aar' - // 0097800c71 + playImplementation 'com.github.celzero:firestack:a6ae729b45:debug@aar' // Work manager - implementation('androidx.work:work-runtime-ktx:2.9.0') { + implementation('androidx.work:work-runtime-ktx:2.9.1') { modules { module("com.google.guava:listenablefuture") { replacedBy("com.google.guava:guava", "listenablefuture is part of guava") @@ -274,8 +273,8 @@ dependencies { leakCanaryImplementation 'com.squareup.leakcanary:leakcanary-android:2.14' - fullImplementation 'androidx.navigation:navigation-fragment-ktx:2.7.7' - fullImplementation 'androidx.navigation:navigation-ui-ktx:2.7.7' + fullImplementation 'androidx.navigation:navigation-fragment-ktx:2.8.0' + fullImplementation 'androidx.navigation:navigation-ui-ktx:2.8.0' fullImplementation 'androidx.biometric:biometric:1.1.0' @@ -284,8 +283,8 @@ dependencies { // for encrypting wireguard configuration files implementation("androidx.security:security-crypto:1.1.0-alpha06") - implementation("androidx.security:security-app-authenticator:1.0.0-alpha03") - androidTestImplementation("androidx.security:security-app-authenticator:1.0.0-alpha03") + implementation("androidx.security:security-app-authenticator:1.0.0-beta01") + androidTestImplementation("androidx.security:security-app-authenticator:1.0.0-beta01") // barcode scanner for wireguard fullImplementation 'com.journeyapps:zxing-android-embedded:4.3.0' From 2c7f51527f96037f6a30ca4943e09a3ae9addcd4 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Wed, 2 Oct 2024 19:48:57 +0530 Subject: [PATCH 165/188] persistent: multiple variables in persistent state for v055o dial strategy retry strategy randomize listen port in advanced wg mode endpoint independent mapping/filtering tcp keep alive (DEBUG only) split dns option use system dns for undelegated domains slowdown mode (DEBUG only) --- .../bravedns/service/PersistentState.kt | 51 ++++++++++++++++--- 1 file changed, 44 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/com/celzero/bravedns/service/PersistentState.kt b/app/src/main/java/com/celzero/bravedns/service/PersistentState.kt index 4759c49b5..6a7d0d4bc 100644 --- a/app/src/main/java/com/celzero/bravedns/service/PersistentState.kt +++ b/app/src/main/java/com/celzero/bravedns/service/PersistentState.kt @@ -20,6 +20,7 @@ import androidx.lifecycle.MutableLiveData import com.celzero.bravedns.R import com.celzero.bravedns.data.AppConfig import com.celzero.bravedns.database.DnsCryptRelayEndpoint +import com.celzero.bravedns.ui.activity.AntiCensorshipActivity import com.celzero.bravedns.util.Constants import com.celzero.bravedns.util.Constants.Companion.INIT_TIME_MS import com.celzero.bravedns.util.Constants.Companion.INVALID_PORT @@ -60,6 +61,12 @@ class PersistentState(context: Context) : SimpleKrate(context), KoinComponent { const val NOTIFICATION_PERMISSION = "notification_permission_request" const val EXCLUDE_APPS_IN_PROXY = "exclude_apps_in_proxy" const val BIOMETRIC_AUTH = "biometric_authentication" + const val ANTI_CENSORSHIP_TYPE = "dial_strategy" + const val RETRY_STRATEGY = "retry_strategy" + const val ENDPOINT_INDEPENDENCE = "endpoint_independence" + const val TCP_KEEP_ALIVE = "tcp_keep_alive" + const val USE_SYSTEM_DNS_FOR_UNDELEGATED_DOMAINS = "use_system_dns_for_undelegated_domains" + const val SLOWDOWN_MODE = "slowdown_mode" } // when vpn is started by the user, this is set to true; set to false when user stops @@ -122,9 +129,8 @@ class PersistentState(context: Context) : SimpleKrate(context), KoinComponent { stringPref("http_proxy_ipaddress").withDefault("http://127.0.0.1:8118") // whether apps subject to the RethinkDNS VPN tunnel can bypass the tunnel on-demand - // default: false for fdroid flavour - var allowBypass by - booleanPref("allow_bypass").withDefault(!Utilities.isFdroidFlavour()) + // default: false + var allowBypass by booleanPref("allow_bypass").withDefault(false) // user set among AppConfig.DnsType enum; RETHINK_REMOTE is default which is Rethink-DoH var dnsType by @@ -191,9 +197,10 @@ class PersistentState(context: Context) : SimpleKrate(context), KoinComponent { private var _blockNewlyInstalledApp by booleanPref("block_new_app").withDefault(false) // user setting to use custom download manager or android's default download manager - // default: false, i.e., use android's default download manager + // default: true, i.e., use in-build download manager, as we see lot of failures with + // android's download manager because of the blocking nature of the app var useCustomDownloadManager by - booleanPref("use_custom_download_managet").withDefault(false) + booleanPref("use_custom_download_managet").withDefault(true) // custom download manager's last generated id var customDownloaderLastGeneratedId by @@ -243,14 +250,17 @@ class PersistentState(context: Context) : SimpleKrate(context), KoinComponent { // make notification persistent (Android 13 and above), default false var persistentNotification by booleanPref("persistent_notification").withDefault(false) - // biometric authentication + // biometric authentication TODO: remove this var biometricAuth by booleanPref("biometric_authentication").withDefault(false) + // bio-metric authentication type + var biometricAuthType by intPref("biometric_authentication_type").withDefault(0) + // enable dns alg var enableDnsAlg by booleanPref("dns_alg").withDefault(false) // default dns url - var defaultDnsUrl by stringPref("default_dns_query").withDefault("") + var defaultDnsUrl by stringPref("default_dns_query").withDefault(Constants.DEFAULT_DNS_LIST[1].url) // packet capture type var pcapMode by intPref("pcap_mode").withDefault(PcapMode.NONE.id) @@ -293,6 +303,33 @@ class PersistentState(context: Context) : SimpleKrate(context), KoinComponent { // camera and mic access var micCamAccess by booleanPref("mic_camera_access").withDefault(false) + // anti-censorship type (auto, split_tls, split_tcp, desync) + var dialStrategy by intPref("dial_strategy").withDefault(AntiCensorshipActivity.DialStrategies.SPLIT_AUTO.mode) + + // retry strategy type (before split, after split, never) + var retryStrategy by intPref("retry_strategy").withDefault(AntiCensorshipActivity.RetryStrategies.RETRY_AFTER_SPLIT.mode) + + // bypass blocking in dns level, decision is made in flow() (see BraveVPNService#flow) + var bypassBlockInDns by booleanPref("bypass_block_in_dns").withDefault(false) + + // randomize listen port for advanced wireguard configuration, default false + // restart of tunnel when wireguard is enabled is required to randomize the port to work properly + // this is not a user facing option, but a developer option + var randomizeListenPort by booleanPref("randomize_listen_port").withDefault(true) + + // endpoint independent mapping/filtering + var endpointIndependence by booleanPref("endpoint_independence").withDefault(false) + + var tcpKeepAlive by booleanPref("tcp_keep_alive").withDefault(false) + + // enable split dns + var splitDns by booleanPref("split_dns").withDefault(false) + + // use system dns for undelegatedDomains + var useSystemDnsForUndelegatedDomains by booleanPref("use_system_dns_for_undelegated_domains").withDefault(false) + + var slowdownMode by booleanPref("slowdown_mode").withDefault(false) + var orbotConnectionStatus: MutableLiveData = MutableLiveData() var median: MutableLiveData = MutableLiveData() var vpnEnabledLiveData: MutableLiveData = MutableLiveData() From 48e5b40f56a1c54a13f136ff25abbc513ea068f0 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Wed, 2 Oct 2024 19:57:44 +0530 Subject: [PATCH 166/188] tunnel: various changes and fixes for v055o - implement dial strategy - set transparency - set crashfd - Support for undelegated domain - randomized listen port for advanced wg - new: dispatchers to manage go-ktx flow - avoid setting system dns if unchanged - logic updates for onQuery and flow calls - new preflow to determine dns for split dns - new inflow() to determine proxy details for connection - various other improvements --- .../celzero/bravedns/net/go/GoVpnAdapter.kt | 106 ++++- .../bravedns/net/manager/ConnectionTracer.kt | 17 +- .../bravedns/service/BraveVPNService.kt | 448 +++++++++++++++--- .../celzero/bravedns/service/VpnController.kt | 7 +- 4 files changed, 477 insertions(+), 101 deletions(-) diff --git a/app/src/main/java/com/celzero/bravedns/net/go/GoVpnAdapter.kt b/app/src/main/java/com/celzero/bravedns/net/go/GoVpnAdapter.kt index eb3dd8f2a..872ad31b0 100644 --- a/app/src/main/java/com/celzero/bravedns/net/go/GoVpnAdapter.kt +++ b/app/src/main/java/com/celzero/bravedns/net/go/GoVpnAdapter.kt @@ -29,6 +29,7 @@ import com.celzero.bravedns.data.AppConfig.Companion.FALLBACK_DNS import com.celzero.bravedns.data.AppConfig.TunnelOptions import com.celzero.bravedns.database.DnsCryptRelayEndpoint import com.celzero.bravedns.database.ProxyEndpoint +import com.celzero.bravedns.scheduler.EnhancedBugReport import com.celzero.bravedns.service.BraveVPNService import com.celzero.bravedns.service.PersistentState import com.celzero.bravedns.service.ProxyManager @@ -37,6 +38,7 @@ import com.celzero.bravedns.service.RethinkBlocklistManager import com.celzero.bravedns.service.TcpProxyHelper import com.celzero.bravedns.service.VpnController import com.celzero.bravedns.service.WireguardManager +import com.celzero.bravedns.ui.activity.AntiCensorshipActivity import com.celzero.bravedns.util.Constants import com.celzero.bravedns.util.Constants.Companion.MAX_ENDPOINT import com.celzero.bravedns.util.Constants.Companion.ONDEVICE_BLOCKLIST_FILE_TAG @@ -50,6 +52,7 @@ import com.celzero.bravedns.util.InternetProtocol import com.celzero.bravedns.util.Utilities import com.celzero.bravedns.util.Utilities.blocklistDir import com.celzero.bravedns.util.Utilities.blocklistFile +import com.celzero.bravedns.util.Utilities.isAtleastS import com.celzero.bravedns.util.Utilities.isPlayStoreFlavour import com.celzero.bravedns.util.Utilities.showToastUiCentered import com.celzero.bravedns.wireguard.Config @@ -61,6 +64,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.koin.core.component.KoinComponent import org.koin.core.component.inject +import settings.Settings /** * This is a VpnAdapter that captures all traffic and routes it through a go-tun2socks instance with @@ -119,6 +123,10 @@ class GoVpnAdapter : KoinComponent { addTransport() setDnsAlg() notifyLoopback() + setDialStrategy() + setTransparency() + setCrashFd() + undelegatedDomains() Logger.v(LOG_TAG_VPN, "GoVpnAdapter initResolverProxiesPcap done") } @@ -133,6 +141,17 @@ class GoVpnAdapter : KoinComponent { Logger.v(LOG_TAG_VPN, "GoVpnAdapter setPcapMode done") } + private suspend fun setCrashFd() { + Logger.v(LOG_TAG_VPN, "GoVpnAdapter setCrashFd") + val crashFd = EnhancedBugReport.getFileToWrite(context) + if (crashFd != null) { + val set = Intra.setCrashFd(crashFd.absolutePath) + Logger.i(LOG_TAG_VPN, "set crash fd: $crashFd, set? $set") + } else { + Logger.w(LOG_TAG_VPN, "crash fd is null, cannot set") + } + } + private suspend fun setRoute(tunnelOptions: TunnelOptions) { Logger.v(LOG_TAG_VPN, "GoVpnAdapter setRoute") try { @@ -767,12 +786,15 @@ class GoVpnAdapter : KoinComponent { val wgConfig = WireguardManager.getConfigById(proxyId) val isOneWg = WireguardManager.getOneWireGuardProxyId() == proxyId - val wgUserSpaceString = wgConfig?.toWgUserspaceString(isOneWg) + val skipListenPort = !isOneWg && persistentState.randomizeListenPort + val wgUserSpaceString = wgConfig?.toWgUserspaceString(skipListenPort) getProxies()?.addProxy(id, wgUserSpaceString) - if (isOneWg) setWireGuardDns(id) + //if (isOneWg) setWireGuardDns(id) + setWireGuardDns(id) // initiate a ping request to the wg proxy initiateWgPing(id) Logger.i(LOG_TAG_VPN, "add wireguard proxy with $id; dns? $isOneWg") + Logger.vv(LOG_TAG_VPN, "wg config: $wgUserSpaceString") } catch (e: Exception) { Logger.e(LOG_TAG_VPN, "err adding wireguard proxy: ${e.message}", e) // do not auto remove failed wg proxy, let the user decide via UI @@ -1225,7 +1247,6 @@ class GoVpnAdapter : KoinComponent { Logger.w(LOG_TAG_VPN, "Tunnel NOT connected, skip get proxies") return null } - val t = System.currentTimeMillis() val px = tunnel.proxies return px } catch (e: Exception) { @@ -1234,14 +1255,14 @@ class GoVpnAdapter : KoinComponent { return null } - suspend fun canRouteIp(wgId: String, ip: String, default: Boolean): Boolean { + suspend fun canRouteIp(proxyId: String, ip: String, default: Boolean): Boolean { return try { - val router = getProxies()?.getProxy(wgId)?.router() ?: return default + val router = getProxies()?.getProxy(proxyId)?.router() ?: return default val res = router.contains(ip) - Logger.i(LOG_TAG_VPN, "canRouteIp($wgId, $ip), res? $res") + Logger.d(LOG_TAG_VPN, "canRouteIp($proxyId, $ip), res? $res") res } catch (e: Exception) { - Logger.w(LOG_TAG_VPN, "err canRouteIp($wgId, $ip): ${e.message}") + Logger.w(LOG_TAG_VPN, "err canRouteIp($proxyId, $ip): ${e.message}") default } } @@ -1251,7 +1272,7 @@ class GoVpnAdapter : KoinComponent { val router = getProxies()?.getProxy(proxyId)?.router() ?: return Pair(false, false) val has4 = router.iP4() val has6 = router.iP6() - Logger.i(LOG_TAG_VPN, "supported ip version($proxyId): has4? $has4, has6? $has6") + Logger.d(LOG_TAG_VPN, "supported ip version($proxyId): has4? $has4, has6? $has6") return Pair(has4, has6) } catch (e: Exception) { Logger.w(LOG_TAG_VPN, "err supported ip version($proxyId): ${e.message}") @@ -1276,7 +1297,7 @@ class GoVpnAdapter : KoinComponent { false } - Logger.i( + Logger.d( LOG_TAG_VPN, "split tunnel proxy($proxyId): ipv4? ${pair.first}, ipv6? ${pair.second}, res? $res" ) @@ -1287,12 +1308,12 @@ class GoVpnAdapter : KoinComponent { } } - suspend fun initiateWgPing(wgId: String) { + suspend fun initiateWgPing(proxyId: String) { try { - val res = getProxies()?.getProxy(wgId)?.ping() - Logger.i(LOG_TAG_VPN, "initiateWgPing($wgId): $res") + val res = getProxies()?.getProxy(proxyId)?.ping() + Logger.i(LOG_TAG_VPN, "initiateWgPing($proxyId): $res") } catch (e: Exception) { - Logger.w(LOG_TAG_VPN, "err initiateWgPing($wgId): ${e.message}") + Logger.w(LOG_TAG_VPN, "err initiateWgPing($proxyId): ${e.message}") } } @@ -1316,9 +1337,9 @@ class GoVpnAdapter : KoinComponent { Logger.i(LOG_TAG_VPN, "low memory, called Intra.lowMem()") } - suspend fun goBuildVersion(): String { + fun goBuildVersion(full: Boolean = false): String { return try { - val version = Intra.build() + val version = Intra.build(full) Logger.i(LOG_TAG_VPN, "go build version: $version") version } catch (e: Exception) { @@ -1327,6 +1348,61 @@ class GoVpnAdapter : KoinComponent { } } + suspend fun setDialStrategy(mode: Int = persistentState.dialStrategy, retry: Int = persistentState.retryStrategy, tcpKeepAlive: Boolean = persistentState.tcpKeepAlive) { + if (!tunnel.isConnected) { + Logger.i(LOG_TAG_VPN, "Tunnel NOT connected, skip set dial strategy") + return + } + try { + Settings.setDialerOpts(mode, retry, tcpKeepAlive) + Logger.i(LOG_TAG_VPN, "set dial strategy: $mode, retry: $retry, tcpKeepAlive: $tcpKeepAlive") + } catch (e: Exception) { + Logger.e(LOG_TAG_VPN, "err set dial strategy: ${e.message}", e) + } + } + + suspend fun setTransparency(eim: Boolean = persistentState.endpointIndependence) { + if (!tunnel.isConnected) { + Logger.i(LOG_TAG_VPN, "Tunnel NOT connected, skip transparency") + return + } + if (!isAtleastS()) { + Intra.transparency(false, false) + Logger.i(LOG_TAG_VPN, "Android version is less than S, set transparency: false") + return + } + // set both endpoint independent mapping and endpoint independent filtering + // as a single value, as for the user is concerned, it is a single setting + Intra.transparency(eim, eim) + Logger.i(LOG_TAG_VPN, "set transparency: $eim") + } + + suspend fun undelegatedDomains(useSystemDns: Boolean = persistentState.useSystemDnsForUndelegatedDomains) { + if (!tunnel.isConnected) { + Logger.i(LOG_TAG_VPN, "Tunnel NOT connected, skip undelegated domains") + return + } + try { + Intra.undelegatedDomains(useSystemDns) + Logger.i(LOG_TAG_VPN, "undelegated domains: $useSystemDns") + } catch (e: Exception) { + Logger.e(LOG_TAG_VPN, "err undelegated domains: ${e.message}", e) + } + } + + suspend fun setSlowdownMode(slowdown: Boolean = persistentState.slowdownMode) { + if (!tunnel.isConnected) { + Logger.i(LOG_TAG_VPN, "Tunnel NOT connected, skip slowdown mode") + return + } + try { + Intra.slowdown(slowdown) + Logger.i(LOG_TAG_VPN, "set slowdown mode: $slowdown") + } catch (e: Exception) { + Logger.e(LOG_TAG_VPN, "err slowdown mode: ${e.message}", e) + } + } + private fun ui(f: suspend () -> Unit) { externalScope.launch(Dispatchers.Main) { f() } } diff --git a/app/src/main/java/com/celzero/bravedns/net/manager/ConnectionTracer.kt b/app/src/main/java/com/celzero/bravedns/net/manager/ConnectionTracer.kt index 8e6b8ff71..7041ff513 100644 --- a/app/src/main/java/com/celzero/bravedns/net/manager/ConnectionTracer.kt +++ b/app/src/main/java/com/celzero/bravedns/net/manager/ConnectionTracer.kt @@ -26,6 +26,12 @@ class ConnectionTracer(ctx: Context) { private const val SEPARATOR = "|" } + enum class CallerSrc { + PREFLOW, + INFLOW, + FLOW + } + private val cm: ConnectivityManager private val uidCache: Cache @@ -47,7 +53,8 @@ class ConnectionTracer(ctx: Context) { sourceIp: String, sourcePort: Int, destIp: String, - destPort: Int + destPort: Int, + caller: CallerSrc ): Int { var uid = Constants.INVALID_UID // android.googlesource.com/platform/development/+/da84168fb/ndk/platforms/android-21/include/linux/in.h @@ -98,7 +105,7 @@ class ConnectionTracer(ctx: Context) { Logger.e(LOG_TAG_VPN, "err getUidQ: " + ex.message, ex) } - if (retryRequired(uid, protocol, destIp, key)){ + if (retryRequired(uid, protocol, destIp, key, caller)){ // change the destination IP to unspecified IP and try again for unconnected UDP val dip = if (IPAddressString(destIp).isIPv6) { @@ -107,7 +114,7 @@ class ConnectionTracer(ctx: Context) { Constants.UNSPECIFIED_IP_IPV4 } val dport = 0 - val res = getUidQ(protocol, sourceIp, sourcePort, dip, dport) + val res = getUidQ(protocol, sourceIp, sourcePort, dip, dport, caller) Logger.d( LOG_TAG_VPN, "retrying with: $protocol, $sourceIp, $sourcePort, $dip, $dport old($destIp, $destPort), res: $res" @@ -121,7 +128,7 @@ class ConnectionTracer(ctx: Context) { } // handle unconnected UDP requests - private fun retryRequired(uid: Int, protocol: Int, destIp: String, key: String): Boolean { + private fun retryRequired(uid: Int, protocol: Int, destIp: String, key: String, caller: CallerSrc): Boolean { if (uid != Constants.INVALID_UID) { // already got the uid, no need to retry return false } @@ -130,7 +137,7 @@ class ConnectionTracer(ctx: Context) { return false } // no need to retry for protocols other than UDP - if (protocol != Protocol.UDP.protocolType) { + if (protocol != Protocol.UDP.protocolType && caller != CallerSrc.INFLOW) { return false } diff --git a/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt b/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt index 4affea208..ad97f8b3c 100644 --- a/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt +++ b/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt @@ -62,6 +62,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer import backend.Backend +import backend.DNSOpts import backend.RDNS import backend.RouterStats import com.celzero.bravedns.R @@ -78,9 +79,11 @@ import com.celzero.bravedns.net.manager.ConnectionTracer import com.celzero.bravedns.receiver.NotificationActionReceiver import com.celzero.bravedns.scheduler.EnhancedBugReport import com.celzero.bravedns.service.FirewallManager.NOTIF_CHANNEL_ID_FIREWALL_ALERTS +import com.celzero.bravedns.service.ProxyManager.ID_WG_BASE import com.celzero.bravedns.ui.HomeScreenActivity import com.celzero.bravedns.ui.NotificationHandlerDialog import com.celzero.bravedns.util.BackgroundAccessibilityService +import com.celzero.bravedns.util.CoFactory import com.celzero.bravedns.util.Constants import com.celzero.bravedns.util.Constants.Companion.INIT_TIME_MS import com.celzero.bravedns.util.Constants.Companion.INVALID_UID @@ -111,6 +114,7 @@ import inet.ipaddr.HostName import inet.ipaddr.IPAddressString import intra.Bridge import intra.Mark +import intra.PreMark import intra.SocketSummary import java.io.IOException import java.net.InetAddress @@ -130,7 +134,6 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineName import kotlinx.coroutines.Deferred import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job import kotlinx.coroutines.MainScope import kotlinx.coroutines.async import kotlinx.coroutines.cancel @@ -146,6 +149,8 @@ import rnet.Tab class BraveVPNService : VpnService(), ConnectionMonitor.NetworkListener, Bridge, OnSharedPreferenceChangeListener { + private val vpnScope = MainScope() + private var connectionMonitor: ConnectionMonitor = ConnectionMonitor(this) // multiple coroutines call both signalStopService and makeOrUpdateVpnAdapter and so @@ -156,6 +161,11 @@ class BraveVPNService : // used mostly for service to adapter creation and updates private var serializer: CoroutineDispatcher = Daemons.make("vpnser") + private var flowDispatcher = Daemons.ioDispatcher("flow", Mark(), vpnScope) + private var inflowDispatcher = Daemons.ioDispatcher("inflow", Mark(), vpnScope) + private var preflowDispatcher = Daemons.ioDispatcher("preflow", PreMark(), vpnScope) + private var dnsQueryDispatcher = Daemons.ioDispatcher("onQuery", DNSOpts(), vpnScope) + // channel to perform wg handshakes private val wgHandshakeChannel = Channel(Channel.CONFLATED) @@ -202,7 +212,6 @@ class BraveVPNService : private val checkpointInterval = TimeUnit.MINUTES.toMillis(1L) private var isLockDownPrevious = AtomicBoolean(false) - private val vpnScope = MainScope() private lateinit var connTracer: ConnectionTracer @@ -414,10 +423,12 @@ class BraveVPNService : srcIp: String, srcPort: Int, dstIp: String, - dstPort: Int + dstPort: Int, + caller: ConnectionTracer.CallerSrc ): Int { + // caller: true - called from flow, false - called from inflow return if (VERSION.SDK_INT >= VERSION_CODES.Q) { - ioAsync("getUidQ") { connTracer.getUidQ(protocol, srcIp, srcPort, dstIp, dstPort) } + ioAsync("getUidQ") { connTracer.getUidQ(protocol, srcIp, srcPort, dstIp, dstPort, caller) } .await() } else { _uid // uid must have been retrieved from procfs by the caller @@ -1879,9 +1890,64 @@ class BraveVPNService : // no-op, no need to restart vpn as no proxy/dns proxy is enabled } } + + PersistentState.ANTI_CENSORSHIP_TYPE -> { + io("antiCensorship") { + setDialStrategy() + } + } + + PersistentState.RETRY_STRATEGY -> { + io("retryStrategy") { + setDialStrategy() + } + } + PersistentState.ENDPOINT_INDEPENDENCE -> { + io("endpointIndependence") { + setTransparency() + } + } + PersistentState.TCP_KEEP_ALIVE -> { + io("tcpKeepAlive") { + setDialStrategy() + } + } + PersistentState.USE_SYSTEM_DNS_FOR_UNDELEGATED_DOMAINS -> { + io("useSystemDnsForUndelegatedDomains") { + undelegatedDomains() + } + } + PersistentState.SLOWDOWN_MODE -> { + io("slowdownMode") { + setSlowdownMode() + } + } } } + private suspend fun undelegatedDomains() { + Logger.i(LOG_TAG_VPN, "use system dns for undelegated domains: ${persistentState.useSystemDnsForUndelegatedDomains}") + vpnAdapter?.undelegatedDomains(persistentState.useSystemDnsForUndelegatedDomains) + } + + private suspend fun setDialStrategy() { + Logger.d( + LOG_TAG_VPN, + "set dial strategy: ${persistentState.dialStrategy}, retry: ${persistentState.retryStrategy}, tcpKeepAlive: ${persistentState.tcpKeepAlive}" + ) + vpnAdapter?.setDialStrategy() + } + + private suspend fun setSlowdownMode() { + Logger.d(LOG_TAG_VPN, "set slowdown mode: ${persistentState.slowdownMode}") + vpnAdapter?.setSlowdownMode() + } + + private suspend fun setTransparency() { + Logger.d(LOG_TAG_VPN, "set endpoint independence: ${persistentState.endpointIndependence}") + vpnAdapter?.setTransparency(persistentState.endpointIndependence) + } + private suspend fun disableAllowBypassIfNeeded() { if (appConfig.isProxyEnabled() && persistentState.allowBypass) { Logger.i(LOG_TAG_VPN, "disabling allowBypass, as proxy is set.") @@ -2201,11 +2267,12 @@ class BraveVPNService : val isRoutesChanged = hasRouteChangedInAutoMode(out) val isBoundNetworksChanged = out.netChanged val isMtuChanged = out.mtuChanged + val prevDns = networks.dnsServers.keys underlyingNetworks = networks Logger.i(LOG_TAG_VPN, "onNetworkConnected: changes: $out for new: $networks") // always reset the system dns server ip of the active network with the tunnel - setNetworkAndDefaultDnsIfNeeded() + setNetworkAndDefaultDnsIfNeeded(prevDns) if (networks.useActive) { setUnderlyingNetworks(null) @@ -2240,14 +2307,16 @@ class BraveVPNService : } } } - // check if already refresh is triggered, if not, trigger refresh + if (!isRoutesChanged && isBoundNetworksChanged) { // Workaround for WireGuard connection issues after network change // WireGuard may fail to connect to the server when the network changes. - // refresh will do a configuration refresh in tunnel to ensure a successful - // reconnection after detecting a network change event Logger.v(LOG_TAG_VPN, "refresh wg after network change") refreshProxies() + // invalidate the catch-all cache when the network changes, ref: #1706 + io("WgCache") { + WireguardManager.invalidateCatchAllCache() + } } // no need to close the existing connections if the bound networks are changed @@ -2357,9 +2426,9 @@ class BraveVPNService : return NetworkChanges(routesChanged, netChanged, mtuChanged) } - private fun setNetworkAndDefaultDnsIfNeeded() { + private fun setNetworkAndDefaultDnsIfNeeded(prevDns: Set? = null) { val currNet = underlyingNetworks - val useActive = currNet == null || currNet.useActive + /*val useActive = currNet == null || currNet.useActive val dnsServers = if ( persistentState.routeRethinkInRethink || // rinr case is same as multiple networks @@ -2398,9 +2467,43 @@ class BraveVPNService : Logger.i(LOG_TAG_VPN, "dns servers for network: $dnsServers") dnsServers } - } + }*/ + + // get dns servers from the first network or active network + val active = connectivityManager.activeNetwork + val dnsServers = if (connectivityManager + .getNetworkCapabilities(active) + ?.hasTransport(NetworkCapabilities.TRANSPORT_VPN) == true) { + Logger.i(LOG_TAG_VPN, "active network is vpn, so no need get dns servers") + mutableListOf() + } else { + val lp = connectivityManager.getLinkProperties(active) + // here dnsServers are validated with underlyingNetworks, so there may be a case + // where v6 address is added when v6 network is not available + // so, dnsServers will have both v4 and v6 addresses + lp?.dnsServers?.toMutableList() ?: mutableListOf() + } + + if (dnsServers.isEmpty()) { + // first network is considered to be active network + val ipv4 = currNet?.ipv4Net?.firstOrNull() + val ipv6 = currNet?.ipv6Net?.firstOrNull() + val dns4 = ipv4?.linkProperties?.dnsServers + val dns6 = ipv6?.linkProperties?.dnsServers + // if active network is not found in the list of networks, then use dns from + // first network + val dl = mutableListOf() + // add all the dns servers from the first network, depending on the current + // route, netstack will make use of the dns servers + dns4?.let { dl.addAll(it) } + dns6?.let { dl.addAll(it) } + Logger.i(LOG_TAG_VPN, "dns servers for network: $dl") + dnsServers.addAll(dl) + } else { + Logger.i(LOG_TAG_VPN, "dns servers for network: $dnsServers") + } - if (dnsServers.isNullOrEmpty()) { + if (dnsServers.isEmpty()) { // TODO: send an alert/notification instead? Logger.w(LOG_TAG_VPN, "No system dns servers found") if (appConfig.isSystemDns()) { @@ -2417,8 +2520,14 @@ class BraveVPNService : } } io("setSystemAndDefaultDns") { - Logger.i(LOG_TAG_VPN, "Setting dns servers: $dnsServers") + val existingDns = determineSystemDns(prevDns?.toList()) val dns = determineSystemDns(dnsServers) + Logger.d(LOG_TAG_VPN, "dns servers: $dns, existing: $existingDns") + if (dns == existingDns) { + Logger.i(LOG_TAG_VPN, "System dns servers are same, no need to set") + return@io + } + Logger.i(LOG_TAG_VPN, "System dns servers changed, set: $dns") // set system dns whenever there is a change in network vpnAdapter?.setSystemDns(dns) // set default dns server for the tunnel if none is set @@ -2528,9 +2637,6 @@ class BraveVPNService : connectionMonitor.onVpnStop() VpnController.onVpnDestroyed() try { - // cancel the job which cancels all coroutines started in this scope - // cancel the jobs before cancelling the scope - vpnScope.coroutineContext[Job]?.cancel("vpnDestroy") vpnScope.cancel("vpnDestroy") } catch (ignored: IllegalStateException) { } catch ( @@ -2977,6 +3083,12 @@ class BraveVPNService : }*/ } + private fun go2kt(co: CoFactory, f: suspend() -> T): T = runBlocking { + // runBlocking blocks the current thread until all coroutines within it are complete + // an call a suspending function from a non-suspending context and obtain the result. + return@runBlocking co.tryDispatch(f) + } + private fun io(s: String, f: suspend () -> Unit) = vpnScope.launch(CoroutineName(s) + Dispatchers.IO) { f() } @@ -2990,38 +3102,40 @@ class BraveVPNService : return vpnScope.async(CoroutineName(s) + Dispatchers.IO) { f() } } - override fun onQuery(fqdn: String?, qtype: Long): backend.DNSOpts = runBlocking { + override fun onQuery(fqdn: String?, qtype: Long): backend.DNSOpts = go2kt(dnsQueryDispatcher) { // queryType: see ResourceRecordTypes.kt logd("onQuery: rcvd query: $fqdn, qtype: $qtype") if (fqdn == null) { Logger.e(LOG_TAG_VPN, "onQuery: fqdn is null") // return block all, as it is not expected to reach here - return@runBlocking makeNsOpts(Backend.BlockAll) + return@go2kt makeNsOpts(Backend.BlockAll) } - if (appConfig.getBraveMode().isDnsMode()) { + val appMode = appConfig.getBraveMode() + if (appMode.isDnsMode()) { val res = getTransportIdForDnsMode(fqdn) logd("onQuery (Dns):$fqdn, dnsx: $res") - return@runBlocking res + return@go2kt res } - if (appConfig.getBraveMode().isDnsFirewallMode()) { - val res = getTransportIdForDnsFirewallMode(fqdn) + if (appMode.isDnsFirewallMode()) { + val res = getTransportIdForDnsFirewallMode(fqdn, true) logd("onQuery (Dns+Firewall):$fqdn, dnsx: $res") - return@runBlocking res + return@go2kt res } val res = makeNsOpts(Backend.Preferred) // should not reach here Logger.e( LOG_TAG_VPN, - "onQuery: unknown mode ${appConfig.getBraveMode()}, $fqdn, returning $res" + "onQuery: unknown mode ${appMode}, $fqdn, returning $res" ) - return@runBlocking res + return@go2kt res } // function to decide which transport id to return on Dns only mode private suspend fun getTransportIdForDnsMode(fqdn: String): backend.DNSOpts { - val tid = determineDnsTransportId() + // useFixedTransport is false in Dns only mode + val tid = determineDnsTransportId(false) // check for global domain rules when (DomainRulesManager.getDomainRule(fqdn, UID_EVERYBODY)) { @@ -3034,20 +3148,24 @@ class BraveVPNService : } // function to decide which transport id to return on DnsFirewall mode - private suspend fun getTransportIdForDnsFirewallMode(fqdn: String): backend.DNSOpts { - val tid = determineDnsTransportId() + // two types of src, from onQuery and from preFlow (onQuery: true, preFlow: false) + private suspend fun getTransportIdForDnsFirewallMode(fqdn: String, src: Boolean = false): backend.DNSOpts { + val useFixedTransport = shouldUseFixedTransport(src) + + val tid = determineDnsTransportId(useFixedTransport) val forceBypassLocalBlocklists = isAppPaused() && VpnController.isVpnLockdown() return if (forceBypassLocalBlocklists) { // if the app is paused and vpn is in lockdown mode, then bypass the local blocklists - makeNsOpts(tid, true) - } else if (FirewallManager.isAnyAppBypassesDns()) { - // if any app is bypassed (dns + firewall) set isBlockFree as true, so that the - // domain is resolved amd the decision is made by in flow() - makeNsOpts(transportIdsAlg(tid), true) + makeNsOpts(appendDnsCacheIfNeeded(tid), true) + } else if (FirewallManager.isAnyAppBypassesDns() || (persistentState.bypassBlockInDns && !persistentState.splitDns)) { + // if any app is bypassed (dns + firewall) or bypassBlockInDns is enabled, then + // set bypass local blocklist as true, so that the domain is resolved and the decision + // is made by in flow() + makeNsOpts(transportIdsAlg(tid, useFixedTransport), true) } else if (DomainRulesManager.isDomainTrusted(fqdn)) { // set isBlockFree as true so that the decision is made by in flow() function - makeNsOpts(transportIdsAlg(tid), true) + makeNsOpts(transportIdsAlg(tid, useFixedTransport), true) } else if ( DomainRulesManager.status(fqdn, UID_EVERYBODY) == DomainRulesManager.Status.BLOCK ) { @@ -3056,13 +3174,27 @@ class BraveVPNService : makeNsOpts(Backend.BlockAll) } else { // no global rule, no app-wise trust, return the tid as it is - makeNsOpts(tid) + makeNsOpts(appendDnsCacheIfNeeded(tid)) + } + } + + private fun shouldUseFixedTransport(src: Boolean): Boolean { + return if (src) { + // if the source is onQuery, then use the fixed transport id only if the alg + // and advanced WireGuard is enabled + // persistentState.enableDnsAlg && WireguardManager.isAdvancedWgActive() + + // instead of above condition, introduce new splitDns setting + persistentState.splitDns && WireguardManager.isAdvancedWgActive() + } else { + // if the source is preFlow, no need to check for advanced WireGuard + false } } - private fun determineDnsTransportId(): String { + private fun determineDnsTransportId(useFixedTransport: Boolean): String { val oneWgId = WireguardManager.getOneWireGuardProxyId() - return if (oneWgId != null) { + val tid = if (oneWgId != null) { ProxyManager.ID_WG_BASE + oneWgId } else if (appConfig.isSystemDns() || (isAppPaused() && VpnController.isVpnLockdown())) { // in vpn-lockdown mode+appPause , use system dns if the app is paused to mimic @@ -3071,6 +3203,15 @@ class BraveVPNService : } else { Backend.Preferred } + return if (useFixedTransport) { + val sb = StringBuilder() + val tr1 = appendDnsCacheIfNeeded(tid) + val tr2 = Backend.Fixed // fixed transport id + sb.append(tr1).append(",").append(tr2) + sb.toString() + } else { + tid + } } private suspend fun makeNsOpts( @@ -3079,19 +3220,24 @@ class BraveVPNService : ): backend.DNSOpts { val opts = backend.DNSOpts() opts.ipcsv = "" // as of now, no suggested ips - opts.tidcsv = appendDnsCacheIfNeeded(tid) + opts.tidcsv = tid opts.pid = proxyIdForOnQuery() opts.noblock = bypassLocalBlocklists return opts } - private fun transportIdsAlg(preferredId: String): String { + private fun transportIdsAlg(preferredId: String, useFixedTransport: Boolean): String { + if (useFixedTransport) { + // case when useFixedTransport is true, then tid will already be appended with Fixed + // so no need to append BlockFree again + return preferredId // ex: CT+Preferred,Fixed + } // case when userPreferredId is Alg, then return BlockFree + tid // tid can be System / ProxyId / Preferred return if (isRethinkDnsEnabled()) { val sb = StringBuilder() val tr1 = appendDnsCacheIfNeeded(Backend.BlockFree) - val tr2 = appendDnsCacheIfNeeded(preferredId) // ideally, it should be Preferred + val tr2 = preferredId // ideally, it should be Preferred sb.append(tr1).append(",").append(tr2) sb.toString() } else { @@ -3167,14 +3313,18 @@ class BraveVPNService : Logger.i(LOG_TAG_VPN, "received null summary for dns") return } - logd("onResponse: $summary") + if (!DEBUG) { + if (summary.id.contains(Backend.Fixed)) { + return + } + } netLogTracker.processDnsLog(summary) setRegionLiveDataIfRequired(summary) } private fun setRegionLiveDataIfRequired(summary: backend.DNSSummary) { - if (summary.region.isNullOrEmpty()) { + if (summary.region == null) { return } @@ -3340,6 +3490,69 @@ class BraveVPNService : } } + override fun preflow( + protocol: Int, + uid: Int, + src: String, + dst: String, + domains: String + ): PreMark = go2kt(preflowDispatcher) { + val first = HostName(src) + val second = HostName(dst) + + val srcIp = if (first.asAddress() == null) "" else first.asAddress().toString() + val srcPort = first.port ?: 0 + val dstIp = if (second.asAddress() == null) "" else second.asAddress().toString() + val dstPort = second.port ?: 0 + + val newUid = if (uid == INVALID_UID) { // fetch uid only if it is invalid + getUid( + uid, + protocol, + srcIp, + srcPort, + dstIp, + dstPort, + ConnectionTracer.CallerSrc.PREFLOW + ) + } else { + uid + } + Logger.d( + LOG_TAG_VPN, + "preflow: $newUid, $srcIp, $srcPort, $dstIp, $dstPort, $domains" + ) + + // check if newUid is part of wireguard proxy, if so, return tid as wireguard proxy id + //val pid = ProxyManager.getProxyIdForApp(newUid) + // if catch-all needs to be considered, then use below code + val config = WireguardManager.getConfigIdForApp(newUid) + val pid = if (config != null) { + ID_WG_BASE + config.id + } else { + "" + } + + if (pid.contains(ID_WG_BASE)) { + val p = PreMark() + p.uid = newUid.toString() + p.tidcsv = pid + Logger.i( + LOG_TAG_VPN, + "preflow: wireguard proxy, returning ${p.tidcsv}, ${p.uid}" + ) + return@go2kt p + } else { + val d = domains.split(",").first() + val opts = getTransportIdForDnsFirewallMode(d) + val p = PreMark() + p.uid = newUid.toString() + p.tidcsv = opts.tidcsv + Logger.i(LOG_TAG_VPN, "preflow: returning ${p.tidcsv}, ${p.uid}") + return@go2kt p + } + } + override fun flow( protocol: Int, _uid: Int, @@ -3349,14 +3562,12 @@ class BraveVPNService : d: String, possibleDomains: String, blocklists: String - ): Mark = runBlocking { - // runBlocking blocks the current thread until all coroutines within it are complete - // an call a suspending function from a non-suspending context and obtain the result. + ): Mark = go2kt(flowDispatcher) { logd("flow: $_uid, $src, $dest, $realIps, $d, $blocklists") handleVpnLockdownStateAsync() - // in case of double loopback, all traffic will be part of rinr instead of rethink's own - // traffic, flip the doubleLoopback flag to true if we need that behavior + // in case of double loopback, all traffic will be part of rinr instead of just rethink's + // own traffic. flip the doubleLoopback flag to true if we need that behavior val doubleLoopback = false val first = HostName(src) @@ -3377,7 +3588,15 @@ class BraveVPNService : } else { fip } - var uid = getUid(_uid, protocol, srcIp, srcPort, dstIp, dstPort) + var uid = getUid( + _uid, + protocol, + srcIp, + srcPort, + dstIp, + dstPort, + ConnectionTracer.CallerSrc.FLOW + ) uid = FirewallManager.appId(uid, isPrimaryUser()) val userId = FirewallManager.userId(uid) @@ -3421,7 +3640,7 @@ class BraveVPNService : logd("flow: dns-over-tls, returning Ipn.Block, $uid") cm.isBlocked = true cm.blockedByRule = FirewallRuleset.RULE14.id - return@runBlocking persistAndConstructFlowResponse(cm, Backend.Block, connId, uid) + return@go2kt persistAndConstructFlowResponse(cm, Backend.Block, connId, uid) } // app is considered as spl when it is selected to forward dns proxy, socks5 or http proxy @@ -3449,20 +3668,22 @@ class BraveVPNService : val proxy = if (trapVpnDns) { Backend.Base + // do not add the trackedCids for dns entries as there will not be any + // onSocketClosed event for dns entries } else { + // add to trackedCids, so that the connection can be removed from the list when the + // connection is closed (onSocketClosed), use: ui to show the active connections + val key = CidKey(cm.connId, uid) + trackedCids.add(key) Backend.Exit } - // add to trackedCids, so that the connection can be removed from the list when the - // connection is closed (onSocketClosed), use: ui to show the active connections - val key = CidKey(cm.connId, uid) - trackedCids.add(key) - return@runBlocking persistAndConstructFlowResponse(cm, proxy, connId, uid, isRethink) + return@go2kt persistAndConstructFlowResponse(cm, proxy, connId, uid, isRethink) } if (trapVpnDns) { logd("flow: dns-request, returning ${Backend.Base}, $uid, $connId") - return@runBlocking persistAndConstructFlowResponse(null, Backend.Base, connId, uid) + return@go2kt persistAndConstructFlowResponse(null, Backend.Base, connId, uid) } processFirewallRequest(cm, anyRealIpBlocked, blocklists, isSplApp) @@ -3470,7 +3691,7 @@ class BraveVPNService : if (cm.isBlocked) { // return Ipn.Block, no need to check for other rules logd("flow: received rule: block, returning Ipn.Block, $connId, $uid") - return@runBlocking persistAndConstructFlowResponse(cm, Backend.Block, connId, uid) + return@go2kt persistAndConstructFlowResponse(cm, Backend.Block, connId, uid) } // add to trackedCids, so that the connection can be removed from the list when the @@ -3478,7 +3699,73 @@ class BraveVPNService : val key = CidKey(cm.connId, uid) trackedCids.add(key) - return@runBlocking determineProxyDetails(cm, doubleLoopback) + return@go2kt determineProxyDetails(cm, doubleLoopback) + } + + override fun inflow(protocol: Int, uid: Int, src: String?, dst: String?): Mark = go2kt(inflowDispatcher) { + val doubleLoopback = false + val first = HostName(src) + val second = HostName(dst) + + val srcIp = if (first.asAddress() == null) "" else first.asAddress().toString() + val srcPort = first.port ?: 0 + val dstIp = if (second.asAddress() == null) "" else second.asAddress().toString() + val dstPort = second.port ?: 0 + + var recvdUid = getUid( + uid, + protocol, + srcIp, + srcPort, + dstIp, + dstPort, + ConnectionTracer.CallerSrc.INFLOW + ) + recvdUid = FirewallManager.appId(recvdUid, isPrimaryUser()) + val userId = FirewallManager.userId(recvdUid) + + logd("inflow: $uid($recvdUid), $srcIp, $srcPort, $dstIp, $dstPort") + + val connId = Utilities.getRandomString(8) + + val connType = + if (isConnectionMetered(dstIp)) { + ConnectionTracker.ConnType.METERED + } else { + ConnectionTracker.ConnType.UNMETERED + } + + val cm = + createConnTrackerMetaData( + uid, + userId, + srcIp, + srcPort, + dstIp, + dstPort, + protocol, + proxyDetails = "", + "", + "", + connId, + connType + ) + + processFirewallRequest(cm, false, "") + + if (cm.isBlocked) { + // return Ipn.Block, no need to check for other rules + logd("inflow: received rule: block, returning Ipn.Block, $connId, $uid") + return@go2kt persistAndConstructFlowResponse(cm, Backend.Block, connId, uid) + } + + // add to trackedCids, so that the connection can be removed from the list when the + // connection is closed (onSocketClosed), use: ui to show the active connections + val key = CidKey(cm.connId, uid) + trackedCids.add(key) + + logd("inflow: determine proxy and other dtls for $connId, $uid") + return@go2kt determineProxyDetails(cm, doubleLoopback) } private suspend fun isSpecialApp(uid: Int): Boolean { @@ -3544,6 +3831,10 @@ class BraveVPNService : val baseOrExit = if (doubleLoopback) { Backend.Base + } else if (connTracker.blockedByRule == FirewallRuleset.RULE9.id) { + // special case: proxied dns traffic should not Backed.Exit as is. Only traffic + // marked with Backend.Base will be handled (proxied) by vpnAdapter's dns-transport + Backend.Base } else { Backend.Exit } @@ -3551,7 +3842,8 @@ class BraveVPNService : val uid = connTracker.uid if (FirewallManager.isAppExcludedFromProxy(uid)) { - logd("flow: app is excluded from proxy, returning Ipn.Base, $connId, $uid") + logd("flow/inflow: app is excluded from proxy, returning Ipn.Base, $connId, $uid") + connTracker.blockedByRule = FirewallRuleset.RULE15.id return persistAndConstructFlowResponse(connTracker, baseOrExit, connId, uid) } @@ -3565,13 +3857,13 @@ class BraveVPNService : return if (ipSupported && canRoute) { val actions = ProxyOperationData(proxyId, ProxyOperation.REFRESH) handleProxyActions(actions) - logd("flow: one-wg is enabled, returning $proxyId, $connId, $uid") + logd("flow/inflow: one-wg is enabled, returning $proxyId, $connId, $uid") persistAndConstructFlowResponse(connTracker, proxyId, connId, uid) } else { // in some configurations the allowed ips will not be 0.0.0.0/0, so the connection // will be dropped, in those cases, return base (connection will be forwarded to // base proxy) - logd("flow: one-wg is enabled, but no route/ip; ret:Ipn.Base, $connId, $uid") + logd("flow/inflow: one-wg is enabled, but no route/ip; ret:Ipn.Base, $connId, $uid") persistAndConstructFlowResponse(connTracker, baseOrExit, connId, uid) } } @@ -3587,7 +3879,7 @@ class BraveVPNService : // trigger the handshake if needed val canRoute = canRouteIp(proxyId, connTracker.destIP, true) logd( - "flow: wg is active/lockdown/catch-all; $proxyId, $connId, $uid; canRoute? $canRoute" + "flow/inflow: wg is active/lockdown/catch-all; $proxyId, $connId, $uid; canRoute? $canRoute" ) return if (canRoute) { val actions = ProxyOperationData(proxyId, ProxyOperation.REFRESH) @@ -3601,7 +3893,7 @@ class BraveVPNService : // carry out this check after wireguard, because wireguard has catchAll and lockdown // if no proxy or dns proxy is enabled, return baseOrExit if (!appConfig.isProxyEnabled() && !appConfig.isDnsProxyActive()) { - logd("flow: no proxy/dnsproxy enabled, returning Ipn.Base, $connId, $uid") + logd("flow/inflow: no proxy/dnsproxy enabled, returning Ipn.Base, $connId, $uid") return persistAndConstructFlowResponse(connTracker, baseOrExit, connId, uid) } @@ -3640,16 +3932,16 @@ class BraveVPNService : val endpoint = appConfig.getConnectedOrbotProxy() val packageName = FirewallManager.getPackageNameByUid(uid) if (endpoint?.proxyAppName == packageName) { - logd("flow: orbot exit for $packageName, $connId, $uid") + logd("flow/inflow: orbot exit for $packageName, $connId, $uid") return persistAndConstructFlowResponse(connTracker, Backend.Exit, connId, uid) } val activeId = ProxyManager.getProxyIdForApp(uid) if (!activeId.contains(ProxyManager.ID_ORBOT_BASE)) { - Logger.e(LOG_TAG_VPN, "flow: orbot proxy is enabled but app is not included") + Logger.e(LOG_TAG_VPN, "flow/inflow: orbot proxy is enabled but app is not included") // pass-through } else { - logd("flow: orbot proxy for $uid, $connId") + logd("flow/inflow: orbot proxy for $uid, $connId") return persistAndConstructFlowResponse( connTracker, ProxyManager.ID_ORBOT_BASE, @@ -3663,14 +3955,14 @@ class BraveVPNService : if (appConfig.isCustomSocks5Enabled()) { val endpoint = appConfig.getSocks5ProxyDetails() val packageName = FirewallManager.getPackageNameByUid(uid) - logd("flow: socks5 proxy is enabled, $packageName, ${endpoint.proxyAppName}") + logd("flow/inflow: socks5 proxy is enabled, $packageName, ${endpoint.proxyAppName}") // do not block the app if the app is set to forward the traffic via socks5 proxy if (endpoint.proxyAppName == packageName) { - logd("flow: socks5 exit for $packageName, $connId, $uid") + logd("flow/inflow: socks5 exit for $packageName, $connId, $uid") return persistAndConstructFlowResponse(connTracker, Backend.Exit, connId, uid) } - logd("flow: socks5 proxy for $connId, $uid") + logd("flow/inflow: socks5 proxy for $connId, $uid") return persistAndConstructFlowResponse( connTracker, ProxyManager.ID_S5_BASE, @@ -3684,11 +3976,11 @@ class BraveVPNService : val packageName = FirewallManager.getPackageNameByUid(uid) // do not block the app if the app is set to forward the traffic via http proxy if (endpoint.proxyAppName == packageName) { - logd("flow: http exit for $packageName, $connId, $uid") + logd("flow/inflow: http exit for $packageName, $connId, $uid") return persistAndConstructFlowResponse(connTracker, Backend.Exit, connId, uid) } - logd("flow: http proxy for $connId, $uid") + logd("flow/inflow: http proxy for $connId, $uid") return persistAndConstructFlowResponse( connTracker, ProxyManager.ID_HTTP_BASE, @@ -3702,12 +3994,12 @@ class BraveVPNService : val packageName = FirewallManager.getPackageNameByUid(uid) // do not block the app if the app is set to forward the traffic via dns proxy if (endpoint?.proxyAppName == packageName) { - logd("flow: dns proxy enabled for $packageName, return exit, $connId, $uid") + logd("flow/inflow: dns proxy enabled for $packageName, return exit, $connId, $uid") return persistAndConstructFlowResponse(connTracker, Backend.Exit, connId, uid) } } - logd("flow: no proxies, $baseOrExit, $connId, $uid") + logd("flow/inflow: no proxies, $baseOrExit, $connId, $uid") return persistAndConstructFlowResponse(connTracker, baseOrExit, connId, uid) } @@ -3832,8 +4124,8 @@ class BraveVPNService : return vpnAdapter?.getRDNS(type) } - suspend fun goBuildVersion(): String { - return vpnAdapter?.goBuildVersion() ?: "" + fun goBuildVersion(full: Boolean): String { + return vpnAdapter?.goBuildVersion(full) ?: "" } private fun persistAndConstructFlowResponse( @@ -3855,7 +4147,7 @@ class BraveVPNService : } else { netLogTracker.writeIpLog(cm) } - logd("flow: connTracker: $cm") + logd("flow/inflow: connTracker: $cm") } val mark = intra.Mark() @@ -3870,12 +4162,12 @@ class BraveVPNService : if (cm == null) { Logger.i( LOG_TAG_VPN, - "flow: returning mark: $mark for connId: $connId, uid: $uid, cm: null" + "flow/inflow: returning mark: $mark for connId: $connId, uid: $uid, cm: null" ) } else { Logger.i( LOG_TAG_VPN, - "flow: returning mark: $mark for src(${cm.sourceIP}: ${cm.sourcePort}), dest(${cm.destIP}:${cm.destPort})" + "flow/inflow: returning mark: $mark for src(${cm.sourceIP}: ${cm.sourcePort}), dest(${cm.destIP}:${cm.destPort})" ) } return mark @@ -3885,7 +4177,7 @@ class BraveVPNService : metadata: ConnTrackerMetaData, anyRealIpBlocked: Boolean = false, blocklists: String = "", - isSplApp: Boolean + isSplApp: Boolean = false ) { val rule = firewall(metadata, anyRealIpBlocked, isSplApp) diff --git a/app/src/main/java/com/celzero/bravedns/service/VpnController.kt b/app/src/main/java/com/celzero/bravedns/service/VpnController.kt index aee3b0b6c..4937c25d9 100644 --- a/app/src/main/java/com/celzero/bravedns/service/VpnController.kt +++ b/app/src/main/java/com/celzero/bravedns/service/VpnController.kt @@ -24,6 +24,7 @@ import android.os.SystemClock import androidx.core.content.ContextCompat import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData +import backend.Backend import backend.RDNS import backend.RouterStats import com.celzero.bravedns.R @@ -101,7 +102,7 @@ object VpnController : KoinComponent { states?.cancel() vpnStartElapsedTime = SystemClock.elapsedRealtime() try { - externalScope?.coroutineContext?.get(Job)?.cancel("VPNController - onVpnDestroyed") + // externalScope?.coroutineContext?.get(Job)?.cancel("VPNController - onVpnDestroyed") externalScope?.cancel("VPNController - onVpnDestroyed") } catch (ignored: IllegalStateException) {} catch ( ignored: CancellationException) {} catch (ignored: Exception) {} @@ -327,8 +328,8 @@ object VpnController : KoinComponent { return braveVpnService?.getRDNS(type) } - suspend fun goBuildVersion(): String { - return braveVpnService?.goBuildVersion() ?: "" + fun goBuildVersion(full: Boolean): String { + return braveVpnService?.goBuildVersion(full) ?: "" } fun protectSocket(socket: Socket) { From b3bc40f3d123e5996ad38a7576c0098bead9bc9c Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Wed, 2 Oct 2024 19:59:32 +0530 Subject: [PATCH 167/188] strings: update/add string literals for v055o --- app/src/main/res/values/strings.xml | 70 ++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f4cbb2ecc..59a3fab2b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -113,6 +113,7 @@ %1$s 🔻 %1$s (%1$s) + %1$s%2$s %1$s %2$s %1$s: %2$s %1$s / %2$s @@ -134,6 +135,7 @@ %1$s/S MB + $ 1 24 @@ -637,6 +639,12 @@ Error creating pcap file, Try again! + Biometric Authentication + None + Immediately + After 5 minute + After 15 minutes + Application Icon @@ -719,6 +727,8 @@ Disable %1$s %1$s may result in connectivity loss. Change setting to let Rethink use data and other resources when in background. + You’ve been using Rethink for %1$s days, which translates to a usage cost of $%2$s. Would you consider sponsoring to sustain its development? + rules @@ -746,6 +756,11 @@ Never proxy DNS Do not proxy DNS over Always-on WireGuard, SOCKS5, HTTP proxies. + Use System DNS for undelegated domains + Use System DNS for undelgated domains like .lan, .internal, etc. + + filtering + Rethink is the easiest way to monitor app activity, circumvent Internet censorship, and firewall apps on your Android device. Rethink is a free and open-source project, led by ex-engineers from Amazon, IBM, and Scientific Games. @@ -834,7 +849,6 @@ dmcardle
fortuna
ignoramous
- Lanius-collaris
PeterDaveHello
santhosh-ponnusamy
SeanBurford
@@ -915,6 +929,12 @@ Advanced DNS filtering (experimental) Assign unique IP per DNS request + Split DNS (experimental) + Use fixed DNS servers for specific apps + + Treat DNS rules as firewall rules (experimental) + DNS blocking will be bypassed during resolution; the decision will be made at connection time. + %1$s (%2$s) @@ -1146,6 +1166,7 @@ Trusted Domain (Univ) Proxied Private DNS + Bypass Proxy No firewall rules matched this connection. app.

To change go to All Apps tab.]]>
@@ -1176,6 +1197,7 @@ domain is set as trusted in Universal domain rules.

To change this behaviour go to Configure DNS tab.]]>

To change go to Proxy from Configure screen.]]>
Private DNS server that doesn\'t exist.]]> + source app is set to bypass proxy rules.

To change go to App-specific Firewall screen.]]>
Attention @@ -1562,6 +1584,52 @@ App Log Logs may contain sensitive information. + + Do not randomize listen port + Do not randomize the WireGuard listen port on Advanced mode. + + Endpoint independent mapping + Allow incoming packets to be forwarded to the same endpoint, regardless of the source IP address. + + Anti-censorship + Use anti-censorship techniques to bypass network restrictions. + + TCP keep alive + Send keep alive packets to prevent the connection from being closed. + + + anti-censorship + Anti-Censorship mode is a feature that helps you bypass censorship and access the internet freely. It is recommended to use this feature only if you are in a country where the internet is censored. + + Never split + Do not split packets when sending them to the server. + + Split auto + Automatically split packets when sending them to the server. + + Split TCP + Split TCP packets when sending them to the server. + + Split TLS + Split TLS packets when sending them to the server. + + Desync (experimental) + Desynchronize packets when sending them to the server. + + retry options + Retry options will help you bypass censorship and access the internet freely. It is recommended to use this feature only if you are in a country where the internet is censored. + + Never + Never retry sending packets. + + Retry with Split + Retry sending packets with split. + + Retry after Split + Retry sending packets after split. + + Retry strategy is disabled for never split mode + App Log Logs may contain sensitive information. + Delete logs? + Delete all logs from this app? + Logs deleted + Do not randomize listen port From 67bb47e3ef007714ad9e6c440daa2f3f4bd880fb Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 5 Oct 2024 19:10:18 +0530 Subject: [PATCH 177/188] tunnel: take first non-unspecified ip from real destination ips --- .../main/java/com/celzero/bravedns/service/BraveVPNService.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt b/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt index 86cdcff19..18bc4d5e7 100644 --- a/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt +++ b/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt @@ -3579,7 +3579,8 @@ class BraveVPNService : val dstPort = second.port ?: 0 val ips = realIps.split(",") - val fip = ips.firstOrNull()?.trim() + // take the first non-unspecified ip as the real destination ip + val fip = ips.firstOrNull { !isUnspecifiedIp(it.trim()) }?.trim() // use realIps; as of now, netstack uses the first ip // TODO: apply firewall rules on all real ips val realDestIp = From 532c77c7051c6273541b1ee927856b89f93bfb5b Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 5 Oct 2024 19:19:58 +0530 Subject: [PATCH 178/188] getUidQ: support for ICMPv4, ICMPv6 protocol --- .../bravedns/net/manager/ConnectionTracer.kt | 43 +++++++++++-------- .../com/celzero/bravedns/util/Protocol.kt | 1 + 2 files changed, 25 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/celzero/bravedns/net/manager/ConnectionTracer.kt b/app/src/main/java/com/celzero/bravedns/net/manager/ConnectionTracer.kt index 7041ff513..6f456fa27 100644 --- a/app/src/main/java/com/celzero/bravedns/net/manager/ConnectionTracer.kt +++ b/app/src/main/java/com/celzero/bravedns/net/manager/ConnectionTracer.kt @@ -32,35 +32,40 @@ class ConnectionTracer(ctx: Context) { FLOW } - private val cm: ConnectivityManager - private val uidCache: Cache - - init { - cm = ctx.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager - // Cache the UID for the next 60 seconds. - // the UID will expire after 60 seconds of the write. - // Key for the cache is protocol, local, remote - uidCache = - CacheBuilder.newBuilder() - .maximumSize(CACHE_BUILDER_MAX_SIZE) - .expireAfterWrite(CACHE_BUILDER_WRITE_EXPIRE_SEC, TimeUnit.SECONDS) - .build() - } + private val cm: ConnectivityManager = ctx.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + // Cache the UID for the next 60 seconds. + // the UID will expire after 60 seconds of the write. + // Key for the cache is protocol, local, remote + private val uidCache: Cache = CacheBuilder.newBuilder() + .maximumSize(CACHE_BUILDER_MAX_SIZE) + .expireAfterWrite(CACHE_BUILDER_WRITE_EXPIRE_SEC, TimeUnit.SECONDS) + .build() @TargetApi(Build.VERSION_CODES.Q) suspend fun getUidQ( - protocol: Int, + proto: Int, sourceIp: String, - sourcePort: Int, + sport: Int, destIp: String, - destPort: Int, + dport: Int, caller: CallerSrc ): Int { var uid = Constants.INVALID_UID - // android.googlesource.com/platform/development/+/da84168fb/ndk/platforms/android-21/include/linux/in.h - if (protocol != Protocol.TCP.protocolType && protocol != Protocol.UDP.protocolType) { + var sourcePort = sport + var destPort = dport + var protocol = proto + + // in-case of ICMP, change the protocol to UDP and source/dest port to 0 + // ref: github.com/Gedsh/InviZible/blob/82a0618662ed2fec0fcb6ec55d030d1b76155924/tordnscrypt/src/main/java/pan/alexander/tordnscrypt/vpn/service/ServiceVPN.java#L540C26-L540C30 + if (protocol == Protocol.ICMP.protocolType || protocol == Protocol.ICMPV6.protocolType) { + sourcePort = 0 + destPort = 0 + protocol = Protocol.UDP.protocolType + } else if (protocol != Protocol.TCP.protocolType && protocol != Protocol.UDP.protocolType) { + // android.googlesource.com/platform/development/+/da84168fb/ndk/platforms/android-21/include/linux/in.h return uid } + val local: InetSocketAddress val remote: InetSocketAddress try { diff --git a/app/src/main/java/com/celzero/bravedns/util/Protocol.kt b/app/src/main/java/com/celzero/bravedns/util/Protocol.kt index 72fda628c..6dfcf4473 100644 --- a/app/src/main/java/com/celzero/bravedns/util/Protocol.kt +++ b/app/src/main/java/com/celzero/bravedns/util/Protocol.kt @@ -33,6 +33,7 @@ enum class Protocol(val protocolType: Int) { GRE(47), ESP(50), AH(51), + ICMPV6(58), MTP(92), BEETPH(94), ENCAP(98), From af82edf8d9152b13b67ed3da04af123abb8f3045 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 5 Oct 2024 19:20:49 +0530 Subject: [PATCH 179/188] tunnel: printstack on netstat dialog in DEBUG mode --- .../main/java/com/celzero/bravedns/net/go/GoVpnAdapter.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/java/com/celzero/bravedns/net/go/GoVpnAdapter.kt b/app/src/main/java/com/celzero/bravedns/net/go/GoVpnAdapter.kt index 546a8b6b0..053d6da7c 100644 --- a/app/src/main/java/com/celzero/bravedns/net/go/GoVpnAdapter.kt +++ b/app/src/main/java/com/celzero/bravedns/net/go/GoVpnAdapter.kt @@ -24,6 +24,7 @@ import android.os.ParcelFileDescriptor import android.widget.Toast import backend.Backend import com.celzero.bravedns.R +import com.celzero.bravedns.RethinkDnsApplication.Companion.DEBUG import com.celzero.bravedns.data.AppConfig import com.celzero.bravedns.data.AppConfig.Companion.FALLBACK_DNS import com.celzero.bravedns.data.AppConfig.TunnelOptions @@ -853,6 +854,10 @@ class GoVpnAdapter : KoinComponent { fun getNetStat(): backend.NetStat? { return try { + if (DEBUG) { + // print the stack trace for debugging purposes + Intra.printStack() + } val stat = tunnel.stat() Logger.i(LOG_TAG_VPN, "net stat: $stat") stat From b758190c224080f923284d1b4e44603588afe6e9 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 5 Oct 2024 19:21:20 +0530 Subject: [PATCH 180/188] netstat: include go stats --- app/src/full/java/com/celzero/bravedns/util/UIUtils.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/full/java/com/celzero/bravedns/util/UIUtils.kt b/app/src/full/java/com/celzero/bravedns/util/UIUtils.kt index b8097318d..bec0c6da2 100644 --- a/app/src/full/java/com/celzero/bravedns/util/UIUtils.kt +++ b/app/src/full/java/com/celzero/bravedns/util/UIUtils.kt @@ -671,8 +671,9 @@ object UIUtils { val nic = stat?.nic()?.toString() val rdnsInfo = stat?.rdnsinfo()?.toString() val nicInfo = stat?.nicinfo()?.toString() + val go = stat?.go()?.toString() - var stats = nic + nicInfo + fwd + ip + icmp + tcp + udp + rdnsInfo + var stats = nic + nicInfo + fwd + ip + icmp + tcp + udp + rdnsInfo + go stats = stats.replace("{", "\n") stats = stats.replace("}", "\n\n") stats = stats.replace(",", "\n") From 8c9d2c79d413f8cc389aecd29b6f90828af7fcb0 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 12 Oct 2024 20:05:46 +0530 Subject: [PATCH 181/188] bump gradle version to 8.5.2 --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 04f65102c..c107cb8db 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.3.1' + classpath 'com.android.tools.build:gradle:8.5.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 1dd036412..e5c4e0520 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Mon Apr 17 17:05:36 IST 2023 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME From 6a6b03059a891938b44df56367e8038049347cc7 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 12 Oct 2024 20:39:49 +0530 Subject: [PATCH 182/188] proxy: support for http proxy across all android versions --- .../com/celzero/bravedns/ui/activity/ProxySettingsActivity.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/activity/ProxySettingsActivity.kt b/app/src/full/java/com/celzero/bravedns/ui/activity/ProxySettingsActivity.kt index 6cf5cd516..8b9da17dd 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/activity/ProxySettingsActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/activity/ProxySettingsActivity.kt @@ -415,10 +415,6 @@ class ProxySettingsActivity : AppCompatActivity(R.layout.fragment_proxy_configur } private fun displayHttpProxyUi() { - if (!isAtleastQ()) { - b.settingsActivityHttpProxyContainer.visibility = View.GONE - return - } val isCustomHttpProxyEnabled = appConfig.isCustomHttpProxyEnabled() b.settingsActivityHttpProxyContainer.visibility = View.VISIBLE b.settingsActivityHttpProxySwitch.isChecked = isCustomHttpProxyEnabled From 69dd5eb602b56b48b159b4bac91a8d5d66ccd040 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 12 Oct 2024 21:13:13 +0530 Subject: [PATCH 183/188] ui: fix search issue in app info and nw conns screens --- .../ui/fragment/ConnectionTrackerFragment.kt | 22 +++++++++++++------ .../celzero/bravedns/database/AppInfoDAO.kt | 12 +++++----- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/ConnectionTrackerFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/ConnectionTrackerFragment.kt index f19337eb6..e0581e4e5 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/ConnectionTrackerFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/ConnectionTrackerFragment.kt @@ -62,6 +62,9 @@ class ConnectionTrackerFragment : private val connectionTrackerRepository by inject() private val persistentState by inject() + private var fromWireGuardScreen: Boolean = false + private var fromUniversalFirewallScreen: Boolean = false + companion object { const val PROTOCOL_FILTER_PREFIX = "P:" private const val QUERY_TEXT_TIMEOUT: Long = 600 @@ -80,15 +83,15 @@ class ConnectionTrackerFragment : initView() if (arguments != null) { val query = arguments?.getString(Constants.SEARCH_QUERY) ?: return - val containsUniv = query.contains(UniversalFirewallSettingsActivity.RULES_SEARCH_ID) - val containsWireGuard = query.contains(NetworkLogsActivity.RULES_SEARCH_ID_WIREGUARD) - if (containsUniv) { + fromUniversalFirewallScreen = query.contains(UniversalFirewallSettingsActivity.RULES_SEARCH_ID) + fromWireGuardScreen = query.contains(NetworkLogsActivity.RULES_SEARCH_ID_WIREGUARD) + if (fromUniversalFirewallScreen) { val rule = query.split(UniversalFirewallSettingsActivity.RULES_SEARCH_ID)[1] filterCategories.add(rule) filterType = TopLevelFilter.BLOCKED viewModel.setFilter(filterQuery, filterCategories, filterType) hideSearchLayout() - } else if (containsWireGuard) { + } else if (fromWireGuardScreen) { val rule = query.split(NetworkLogsActivity.RULES_SEARCH_ID_WIREGUARD)[1] filterQuery = rule filterType = TopLevelFilter.ALL @@ -146,9 +149,14 @@ class ConnectionTrackerFragment : recyclerAdapter.addLoadStateListener { if (it.append.endOfPaginationReached) { if (recyclerAdapter.itemCount < 1) { - b.connectionListLogsDisabledTv.text = getString(R.string.ada_ip_no_connection) - b.connectionListLogsDisabledTv.visibility = View.VISIBLE - b.connectionCardViewTop.visibility = View.GONE + if (fromUniversalFirewallScreen || fromWireGuardScreen) { + b.connectionListLogsDisabledTv.text = getString(R.string.ada_ip_no_connection) + b.connectionListLogsDisabledTv.visibility = View.VISIBLE + b.connectionCardViewTop.visibility = View.GONE + } else { + b.connectionListLogsDisabledTv.visibility = View.GONE + b.connectionCardViewTop.visibility = View.VISIBLE + } } else { b.connectionListLogsDisabledTv.visibility = View.GONE b.connectionCardViewTop.visibility = View.VISIBLE diff --git a/app/src/main/java/com/celzero/bravedns/database/AppInfoDAO.kt b/app/src/main/java/com/celzero/bravedns/database/AppInfoDAO.kt index fdb5aed0f..013063e5f 100644 --- a/app/src/main/java/com/celzero/bravedns/database/AppInfoDAO.kt +++ b/app/src/main/java/com/celzero/bravedns/database/AppInfoDAO.kt @@ -54,7 +54,7 @@ interface AppInfoDAO { @Query("select * from AppInfo order by appCategory, uid") fun getAllAppDetails(): List @Query( - "select * from AppInfo where isSystemApp = 1 and (appName like :search or uid like :search or packageName like :search) and firewallStatus in (:firewall) and connectionStatus in (:connectionStatus) or isProxyExcluded in (:isProxyExcluded) order by lower(appName)" + "select * from AppInfo where isSystemApp = 1 and (appName like :search or uid like :search or packageName like :search) and (firewallStatus in (:firewall) or isProxyExcluded in (:isProxyExcluded)) and connectionStatus in (:connectionStatus) order by lower(appName)" ) fun getSystemApps( search: String, @@ -64,7 +64,7 @@ interface AppInfoDAO { ): PagingSource @Query( - "select * from AppInfo where isSystemApp = 1 and (appName like :search or uid like :search or packageName like :search) and appCategory in (:filter) and firewallStatus in (:firewall) and connectionStatus in (:connectionStatus) or isProxyExcluded in (:isProxyExcluded) order by lower(appName)" + "select * from AppInfo where isSystemApp = 1 and (appName like :search or uid like :search or packageName like :search) and appCategory in (:filter) and (firewallStatus in (:firewall) or isProxyExcluded in (:isProxyExcluded)) and connectionStatus in (:connectionStatus) order by lower(appName)" ) fun getSystemApps( search: String, @@ -75,7 +75,7 @@ interface AppInfoDAO { ): PagingSource @Query( - "select * from AppInfo where isSystemApp = 0 and (appName like :search or uid like :search or packageName like :search) and firewallStatus in (:firewall) and connectionStatus in (:connectionStatus) or isProxyExcluded in (:isProxyExcluded) order by lower(appName)" + "select * from AppInfo where isSystemApp = 0 and (appName like :search or uid like :search or packageName like :search) and (firewallStatus in (:firewall) or isProxyExcluded in (:isProxyExcluded)) and connectionStatus in (:connectionStatus) order by lower(appName)" ) fun getInstalledApps( search: String, @@ -85,7 +85,7 @@ interface AppInfoDAO { ): PagingSource @Query( - "select * from AppInfo where isSystemApp = 0 and (appName like :search or uid like :search or packageName like :search) and appCategory in (:filter) and firewallStatus in (:firewall) and connectionStatus in (:connectionStatus) or isProxyExcluded in (:isProxyExcluded) order by lower(appName)" + "select * from AppInfo where isSystemApp = 0 and (appName like :search or uid like :search or packageName like :search) and appCategory in (:filter) and (firewallStatus in (:firewall) or isProxyExcluded in (:isProxyExcluded)) and connectionStatus in (:connectionStatus) order by lower(appName)" ) fun getInstalledApps( search: String, @@ -96,7 +96,7 @@ interface AppInfoDAO { ): PagingSource @Query( - "select * from AppInfo where (appName like :search or uid like :search or packageName like :search) and firewallStatus in (:firewall) and connectionStatus in (:connectionStatus) or isProxyExcluded in (:isProxyExcluded) order by lower(appName)" + "select * from AppInfo where (appName like :search or uid like :search or packageName like :search) and (firewallStatus in (:firewall) or isProxyExcluded in (:isProxyExcluded)) and connectionStatus in (:connectionStatus) order by lower(appName)" ) fun getAppInfos( search: String, @@ -106,7 +106,7 @@ interface AppInfoDAO { ): PagingSource @Query( - "select * from AppInfo where (appName like :search or uid like :search or packageName like :search) and appCategory in (:filter) and firewallStatus in (:firewall) and connectionStatus in (:connectionStatus) or isProxyExcluded in (:isProxyExcluded) order by lower(appName)" + "select * from AppInfo where (appName like :search or uid like :search or packageName like :search) and appCategory in (:filter) and (firewallStatus in (:firewall) or isProxyExcluded in (:isProxyExcluded)) and connectionStatus in (:connectionStatus) order by lower(appName)" ) fun getAppInfos( search: String, From eb544cb9b7148fbb35933f8b08a8686fdb0eb70b Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 12 Oct 2024 21:23:49 +0530 Subject: [PATCH 184/188] fix #1733: resolve ClassCastException when sending multiple email attachments --- .../bravedns/ui/fragment/AboutFragment.kt | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/app/src/full/java/com/celzero/bravedns/ui/fragment/AboutFragment.kt b/app/src/full/java/com/celzero/bravedns/ui/fragment/AboutFragment.kt index ab4028831..b91318805 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/fragment/AboutFragment.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/fragment/AboutFragment.kt @@ -382,12 +382,14 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener, K private fun emailBugReport() { try { // get the rethink.tombstone file - val tombstoneFile = EnhancedBugReport.getTombstoneZipFile(requireContext()) - // Get the bug_report.zip file + val tombstoneFile:File? = EnhancedBugReport.getTombstoneZipFile(requireContext()) + + // get the bug_report.zip file val dir = requireContext().filesDir val file = File(getZipFileName(dir)) - val uri = getFileUri(file) + val uri = getFileUri(file) ?: throw Exception("file uri is null") + // create an intent for sending email with or without multiple attachments val emailIntent = if (tombstoneFile != null) { Intent(Intent.ACTION_SEND_MULTIPLE) } else { @@ -399,14 +401,19 @@ class AboutFragment : Fragment(R.layout.fragment_about), View.OnClickListener, K Intent.EXTRA_SUBJECT, getString(R.string.about_mail_bugreport_subject) ) - emailIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.about_mail_bugreport_text)) - // attach extra as list or single file based on the availability + // attach extra files (either as a list or single file based on availability) if (tombstoneFile != null) { - val tombstoneUri = getFileUri(tombstoneFile) + val tombstoneUri = + getFileUri(tombstoneFile) ?: throw Exception("tombstoneUri is null") + val uriList = arrayListOf(uri, tombstoneUri) // send multiple attachments - emailIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, arrayListOf(uri, tombstoneUri)) + emailIntent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, uriList) } else { + // ensure EXTRA_TEXT is passed correctly as an ArrayList + val bugReportText = getString(R.string.about_mail_bugreport_text) + val bugReportTextList = arrayListOf(bugReportText) + emailIntent.putCharSequenceArrayListExtra(Intent.EXTRA_TEXT, bugReportTextList) emailIntent.putExtra(Intent.EXTRA_STREAM, uri) } Logger.i(LOG_TAG_UI, "email with attachment: $uri, ${tombstoneFile?.path}") From a33539f399a8af06e4ff3a128ee0099a14cb1303 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sat, 12 Oct 2024 21:25:09 +0530 Subject: [PATCH 185/188] docs: add comments for wgQuickString and wgUserSpaceString --- .../celzero/bravedns/wireguard/WgInterface.kt | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/app/src/main/java/com/celzero/bravedns/wireguard/WgInterface.kt b/app/src/main/java/com/celzero/bravedns/wireguard/WgInterface.kt index d836250bc..5f7e41c32 100644 --- a/app/src/main/java/com/celzero/bravedns/wireguard/WgInterface.kt +++ b/app/src/main/java/com/celzero/bravedns/wireguard/WgInterface.kt @@ -154,6 +154,34 @@ class WgInterface private constructor(builder: Builder) { * file. * * @return The `Interface` represented as a series of "Key = Value" lines + * + * below is the sample file content for the Interface section, similar will be stored in the + * wg[0-9].conf file + * [Interface] + * Address = 100.80.213.126/32 + * DNS = 10.255.255.3 + * PrivateKey = KBdV2Iv+poXZkCOZukSo30modVHqyO5+GCdfhP4n40I= + * + * [Peer] + * AllowedIPs = 0.0.0.0/0, ::/0 + * Endpoint = yul-359-wg.whiskergalaxy.com:443 + * Endpoint = yul-359-wg.whiskergalaxy.com:443 + * PreSharedKey = VQoBVQxMmgttgGh9JCBwOPiwnFYV6/Py1tRi/f38DEI= + * PublicKey = nfFRpFZ0ZXWVoz8C4gP5ti7V1snFT1gV8EcIxTWJtB4= + * + * below is converted to wg-quick format + * [Interface] + * Address = 100.80.213.126/32 + * DNS = 10.255.255.3 + * PrivateKey = KBdV2Iv+poXZkCOZukSo30modVHqyO5+GCdfhP4n40I= + * + * [Peer] + * AllowedIPs = 0.0.0.0/0, ::/0 + * Endpoint = yul-359-wg.whiskergalaxy.com:443 + * Endpoint = yul-359-wg.whiskergalaxy.com:443 + * PreSharedKey = VQoBVQxMmgttgGh9JCBwOPiwnFYV6/Py1tRi/f38DEI= + * PublicKey = nfFRpFZ0ZXWVoz8C4gP5ti7V1snFT1gV8EcIxTWJtB4= + * */ fun toWgQuickString(): String { val sb = StringBuilder() @@ -187,6 +215,23 @@ class WgInterface private constructor(builder: Builder) { * not all attributes are included in this representation. * * @return the `Interface` represented as a series of "KEY=VALUE" lines + * + * see #toWgQuickString() for more details on the format + * + * below is the converted format for the userspace string + * + * private_key=281755d88bfea685d9902399ba44a8df49a87551eac8ee7e18275f84fe27e342 + * address=100.80.213.126/32 + * dns=10.255.255.3 + * mtu=1280 + * replace_peers=true + * public_key=9df151a45674657595a33f02e203f9b62ed5d6c9c54f5815f04708c53589b41e + * allowed_ip=0.0.0.0/0 + * allowed_ip=::/0 + * endpoint=172.98.68.207:443 + * endpoint=yul-359-wg.whiskergalaxy.com:443 + * preshared_key=550a01550c4c9a0b6d80687d24207038f8b09c5615ebf3f2d6d462fdfdfc0c42 + * */ fun toWgUserspaceString(skipListenPort: Boolean): String { val dnsServerStrings = From dd7c04fc9ef98c0c8376615785216b7629d1ee9f Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Tue, 22 Oct 2024 16:43:10 +0530 Subject: [PATCH 186/188] ui: fix search issue in app info screen --- .../java/com/celzero/bravedns/viewmodel/AppInfoViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/full/java/com/celzero/bravedns/viewmodel/AppInfoViewModel.kt b/app/src/full/java/com/celzero/bravedns/viewmodel/AppInfoViewModel.kt index 29001b1b8..b85c95530 100644 --- a/app/src/full/java/com/celzero/bravedns/viewmodel/AppInfoViewModel.kt +++ b/app/src/full/java/com/celzero/bravedns/viewmodel/AppInfoViewModel.kt @@ -62,7 +62,7 @@ class AppInfoViewModel(private val appInfoDAO: AppInfoDAO) : ViewModel() { if (filter == bypassFilter) { return setOf(1) } - return setOf(0, 1) + return setOf() // empty set (as query uses or condition) } private fun allApps(searchString: String): LiveData> { From 096eeb4c7fd626da660191c7c63be3ba8ea1e0a4 Mon Sep 17 00:00:00 2001 From: hussainmohd-a Date: Sun, 27 Oct 2024 20:27:28 +0530 Subject: [PATCH 187/188] ui: new icons for settings --- .../res/drawable/ic_dns_rules_as_firewall.xml | 39 +++++++++++++++++++ app/src/main/res/drawable/ic_experimental.xml | 10 +++++ app/src/main/res/drawable/ic_rethink_plus.xml | 9 +++++ .../main/res/drawable/ic_single_threaded.xml | 10 +++++ .../res/drawable/ic_undelegated_domain.xml | 10 +++++ .../res/layout/fragment_dns_configure.xml | 6 +-- 6 files changed, 80 insertions(+), 4 deletions(-) create mode 100644 app/src/main/res/drawable/ic_dns_rules_as_firewall.xml create mode 100644 app/src/main/res/drawable/ic_experimental.xml create mode 100644 app/src/main/res/drawable/ic_rethink_plus.xml create mode 100644 app/src/main/res/drawable/ic_single_threaded.xml create mode 100644 app/src/main/res/drawable/ic_undelegated_domain.xml diff --git a/app/src/main/res/drawable/ic_dns_rules_as_firewall.xml b/app/src/main/res/drawable/ic_dns_rules_as_firewall.xml new file mode 100644 index 000000000..272626764 --- /dev/null +++ b/app/src/main/res/drawable/ic_dns_rules_as_firewall.xml @@ -0,0 +1,39 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_experimental.xml b/app/src/main/res/drawable/ic_experimental.xml new file mode 100644 index 000000000..f0136ce20 --- /dev/null +++ b/app/src/main/res/drawable/ic_experimental.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_rethink_plus.xml b/app/src/main/res/drawable/ic_rethink_plus.xml new file mode 100644 index 000000000..e3c3049d8 --- /dev/null +++ b/app/src/main/res/drawable/ic_rethink_plus.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_single_threaded.xml b/app/src/main/res/drawable/ic_single_threaded.xml new file mode 100644 index 000000000..d1eb3ce78 --- /dev/null +++ b/app/src/main/res/drawable/ic_single_threaded.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_undelegated_domain.xml b/app/src/main/res/drawable/ic_undelegated_domain.xml new file mode 100644 index 000000000..a43e82ba0 --- /dev/null +++ b/app/src/main/res/drawable/ic_undelegated_domain.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/fragment_dns_configure.xml b/app/src/main/res/layout/fragment_dns_configure.xml index 3265131b9..e2bf7187f 100644 --- a/app/src/main/res/layout/fragment_dns_configure.xml +++ b/app/src/main/res/layout/fragment_dns_configure.xml @@ -543,7 +543,7 @@ android:layout_marginEnd="5dp" android:layout_marginBottom="5dp" android:padding="10dp" - android:src="@drawable/bs_dns_home_screen" /> + android:src="@drawable/ic_dns_rules_as_firewall" /> @@ -856,7 +854,7 @@ android:layout_marginEnd="5dp" android:layout_marginBottom="5dp" android:padding="10dp" - android:src="@drawable/ic_prevent_dns_leaks" /> + android:src="@drawable/ic_undelegated_domain" /> Date: Sun, 27 Oct 2024 20:28:04 +0530 Subject: [PATCH 188/188] logger: inc the batch size for console logs --- .../com/celzero/bravedns/service/NetLogTracker.kt | 11 +++++++++-- .../java/com/celzero/bravedns/util/NetLogBatcher.kt | 5 ++--- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/celzero/bravedns/service/NetLogTracker.kt b/app/src/main/java/com/celzero/bravedns/service/NetLogTracker.kt index c60b49a13..6cbd55b5b 100644 --- a/app/src/main/java/com/celzero/bravedns/service/NetLogTracker.kt +++ b/app/src/main/java/com/celzero/bravedns/service/NetLogTracker.kt @@ -69,6 +69,11 @@ internal constructor( private var rrBatcher: NetLogBatcher? = null private var consoleLogBatcher: NetLogBatcher? = null + // dispatch buffer to consumer if greater than batch size for dns, ip and rr logs + private val logBatchSize = 20 + // dispatch buffer to consumer if greater than batch size, for console logs + private val consoleLogBatchSize = 100 + // a single thread to run sig and batch co-routines in; // to avoid use of mutex/semaphores over shared-state // looper is never closed / cancelled and is always active @@ -85,11 +90,12 @@ internal constructor( serializer("restart", looper) { // create new batchers on every new scope as their lifecycle is tied to the scope - val b1 = NetLogBatcher("dns", looper, dnsdb::insertBatch) + val b1 = NetLogBatcher("dns", looper, logBatchSize, dnsdb::insertBatch) val b2 = NetLogBatcher( "ip", looper, + logBatchSize, ipdb::insertBatch, ipdb::updateBatch ) @@ -97,11 +103,12 @@ internal constructor( NetLogBatcher( "rr", looper, + logBatchSize, ipdb::insertRethinkBatch, ipdb::updateRethinkBatch ) val b4 = - NetLogBatcher("console", consoleLogLooper, consoleLogDb::insertBatch) + NetLogBatcher("console", consoleLogLooper, consoleLogBatchSize, consoleLogDb::insertBatch) b1.begin(s) b2.begin(s) diff --git a/app/src/main/java/com/celzero/bravedns/util/NetLogBatcher.kt b/app/src/main/java/com/celzero/bravedns/util/NetLogBatcher.kt index 839aca302..9f31f42c8 100644 --- a/app/src/main/java/com/celzero/bravedns/util/NetLogBatcher.kt +++ b/app/src/main/java/com/celzero/bravedns/util/NetLogBatcher.kt @@ -35,8 +35,9 @@ import kotlinx.coroutines.withContext class NetLogBatcher( private val tag: String, private val looper: CoroutineDispatcher, + private val batchSize: Int, private val processor: suspend (List) -> Unit, - private val updator: suspend (List) -> Unit = { _ -> } + private val updator: suspend (List) -> Unit = { _ -> }, ) { companion object { private const val DEBUG = true @@ -49,8 +50,6 @@ class NetLogBatcher( private val nsig = CoroutineName(tag + "Signal") private val ncons = CoroutineName(tag + "Consumer") // writes batches to db private val closed = AtomicBoolean(false) - // dispatch buffer to consumer if greater than batch size - private val batchSize = 20 // no of batch-sized buffers to hold in a channel private val qsize = 2