diff --git a/app/build.gradle b/app/build.gradle index 214cc9bfd..ae79568f5 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -249,8 +249,8 @@ dependencies { fullImplementation 'com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.5.9' // from: https://jitpack.io/#celzero/firestack - download 'com.github.celzero:firestack:6cc4d626df@aar' - implementation 'com.github.celzero:firestack:6cc4d626df@aar' + download 'com.github.celzero:firestack:ad33ecd668@aar' + implementation 'com.github.celzero:firestack:ad33ecd668@aar' // Work manager implementation('androidx.work:work-runtime-ktx:2.9.0') { diff --git a/app/src/main/java/com/celzero/bravedns/util/Logger.kt b/app/src/fdroid/java/com/celzero/bravedns/util/Logger.kt similarity index 96% rename from app/src/main/java/com/celzero/bravedns/util/Logger.kt rename to app/src/fdroid/java/com/celzero/bravedns/util/Logger.kt index 3fc82aca5..21ab4e045 100644 --- a/app/src/main/java/com/celzero/bravedns/util/Logger.kt +++ b/app/src/fdroid/java/com/celzero/bravedns/util/Logger.kt @@ -70,6 +70,10 @@ object Logger : KoinComponent { log(tag, message, LoggerType.ERROR, e) } + fun crash(tag: String, message: String, e: Exception) { + log(tag, message, LoggerType.ERROR, e) + } + fun updateConfigLevel(level: Long) { logLevel = level } diff --git a/app/src/full/java/com/celzero/bravedns/customdownloader/RetrofitManager.kt b/app/src/full/java/com/celzero/bravedns/customdownloader/RetrofitManager.kt index 63031fb74..bda1981e6 100644 --- a/app/src/full/java/com/celzero/bravedns/customdownloader/RetrofitManager.kt +++ b/app/src/full/java/com/celzero/bravedns/customdownloader/RetrofitManager.kt @@ -118,7 +118,7 @@ class RetrofitManager { } } } catch (e: Exception) { - Logger.e( + Logger.crash( Logger.LOG_TAG_DOWNLOAD, "err while getting custom dns: ${e.message}", e 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 bbf1ef752..14836f859 100644 --- a/app/src/full/java/com/celzero/bravedns/download/BlocklistDownloadHelper.kt +++ b/app/src/full/java/com/celzero/bravedns/download/BlocklistDownloadHelper.kt @@ -164,7 +164,7 @@ class BlocklistDownloadHelper { return processCheckDownloadResponse(r) } } catch (ex: Exception) { - Logger.e(LOG_TAG_DOWNLOAD, "exception in checkBlocklistUpdate: ${ex.message}", ex) + Logger.crash(LOG_TAG_DOWNLOAD, "exception in checkBlocklistUpdate: ${ex.message}", ex) } Logger.i( LOG_TAG_DOWNLOAD, @@ -204,7 +204,7 @@ class BlocklistDownloadHelper { return BlocklistUpdateServerResponse(version, shouldUpdate, timestamp) } catch (e: JSONException) { - Logger.e(LOG_TAG_DOWNLOAD, "Error in parsing the response: ${e.message}", e) + Logger.crash(LOG_TAG_DOWNLOAD, "Error in parsing the response: ${e.message}", e) } return null } 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 04b6b1181..5b2a468a5 100644 --- a/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt +++ b/app/src/full/java/com/celzero/bravedns/service/WireguardManager.kt @@ -407,6 +407,7 @@ object WireguardManager : KoinComponent { } fun getConfigIdForApp(uid: Int): WgConfigFilesImmutable? { + // this method does not account the settings "Bypass all proxies" which is app-specific val configId = ProxyManager.getProxyIdForApp(uid) if (configId == "" || !configId.contains(ProxyManager.ID_WG_BASE)) { Logger.d(LOG_TAG_PROXY, "app config mapping not found for uid: $uid") 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 81649ecc7..aa2d79071 100644 --- a/app/src/full/java/com/celzero/bravedns/ui/HomeScreenActivity.kt +++ b/app/src/full/java/com/celzero/bravedns/ui/HomeScreenActivity.kt @@ -501,7 +501,7 @@ class HomeScreenActivity : AppCompatActivity(R.layout.activity_home_screen) { installStateUpdatedListener ) // Might be play updater or web updater } catch (e: Exception) { - Logger.e(LOG_TAG_APP_UPDATE, "err in app update check: ${e.message}", e) + Logger.crash(LOG_TAG_APP_UPDATE, "err in app update check: ${e.message}", e) showDownloadDialog( AppUpdater.InstallSource.STORE, getString(R.string.download_update_dialog_failure_title), 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 3deca080b..4c55004e4 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 @@ -439,6 +439,19 @@ class ConnTrackerBottomSheet : BottomSheetDialogFragment(), KoinComponent { if (a == fStatus && c == connStatus) return@io + if (VpnController.isVpnLockdown() && fStatus.isExclude()) { + uiCtx { + // reset the spinner to previous selection + updateFirewallRulesUi(a, c) + showToastUiCentered( + requireContext(), + getString(R.string.hsf_exclude_error), + Toast.LENGTH_LONG + ) + } + return@io + } + Logger.i( LOG_TAG_FIREWALL, "Change in firewall rule for app uid: ${info?.uid}, firewall status: $fStatus, conn status: $connStatus" 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 285be336d..c2d46aa44 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 @@ -1161,6 +1161,7 @@ class HomeScreenFragment : Fragment(R.layout.fragment_home_screen) { // versions. // VpnService.prepare() is now registered with requireContext() instead of context. // Issue #469 + Logger.i(LOG_TAG_VPN, "Preparing VPN service") VpnService.prepare(requireContext()) } catch (e: NullPointerException) { // This exception is not mentioned in the documentation, but it has been encountered @@ -1172,9 +1173,11 @@ class HomeScreenFragment : Fragment(R.layout.fragment_home_screen) { // If the VPN.prepare() is not null, then the first time VPN dialog is shown, Show info // dialog before that. if (prepareVpnIntent != null) { + Logger.i(LOG_TAG_VPN, "VPN service is prepared") showFirstTimeVpnDialog(prepareVpnIntent) return false } + Logger.i(LOG_TAG_VPN, "VPN service is prepared, starting VPN service") return true } 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 9ea5b7388..72deb940f 100644 --- a/app/src/full/java/com/celzero/bravedns/viewmodel/AppConnectionsViewModel.kt +++ b/app/src/full/java/com/celzero/bravedns/viewmodel/AppConnectionsViewModel.kt @@ -75,10 +75,12 @@ class AppConnectionsViewModel(private val nwlogDao: ConnectionTrackerDAO) : View 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 @@ -102,10 +104,10 @@ class AppConnectionsViewModel(private val nwlogDao: ConnectionTrackerDAO) : View private fun fetchIpLogs(uid: Int, input: String): LiveData> { val to = getStartTime() return if (input.isEmpty()) { - Pager(pagingConfig) { nwlogDao.getAppIpLogs(uid, to) } - } else { - Pager(pagingConfig) { nwlogDao.getAppIpLogsFiltered(uid, to, "%$input%") } - } + Pager(pagingConfig) { nwlogDao.getAppIpLogs(uid, to) } + } else { + Pager(pagingConfig) { nwlogDao.getAppIpLogsFiltered(uid, to, "%$input%") } + } .liveData .cachedIn(viewModelScope) } @@ -113,10 +115,10 @@ class AppConnectionsViewModel(private val nwlogDao: ConnectionTrackerDAO) : View private fun fetchAppDomainLogs(uid: Int, input: String): LiveData> { val to = getStartTime() return if (input.isEmpty()) { - Pager(pagingConfig) { nwlogDao.getAppDomainLogs(uid, to) } - } else { - Pager(pagingConfig) { nwlogDao.getAppDomainLogsFiltered(uid, to, "%$input%") } - } + Pager(pagingConfig) { nwlogDao.getAppDomainLogs(uid, to) } + } else { + Pager(pagingConfig) { nwlogDao.getAppDomainLogsFiltered(uid, to, "%$input%") } + } .liveData .cachedIn(viewModelScope) } @@ -134,13 +136,15 @@ class AppConnectionsViewModel(private val nwlogDao: ConnectionTrackerDAO) : View } fun getDomainLogsLimited(uid: Int): LiveData> { - return Pager(pagingConfig) { nwlogDao.getAppDomainLogsLimited(uid) } + val to = System.currentTimeMillis() - ONE_WEEK_MILLIS + return Pager(pagingConfig) { nwlogDao.getAppDomainLogsLimited(uid, to) } .liveData .cachedIn(viewModelScope) } fun getIpLogsLimited(uid: Int): LiveData> { - return Pager(pagingConfig) { nwlogDao.getAppIpLogsLimited(uid) } + val to = System.currentTimeMillis() - ONE_WEEK_MILLIS + return Pager(pagingConfig) { nwlogDao.getAppIpLogsLimited(uid, to) } .liveData .cachedIn(viewModelScope) } diff --git a/app/src/full/res/layout/list_item_app_domain_details.xml b/app/src/full/res/layout/list_item_app_domain_details.xml index 061ec920d..8c8dd58fd 100644 --- a/app/src/full/res/layout/list_item_app_domain_details.xml +++ b/app/src/full/res/layout/list_item_app_domain_details.xml @@ -90,7 +90,7 @@ android:background="@drawable/drawable_purple_gradient" android:fontFamily="sans-serif-light" android:gravity="center" - android:maxWidth="40dp" + android:maxWidth="60dp" android:minWidth="40dp" android:padding="5dp" android:singleLine="true" diff --git a/app/src/full/res/layout/list_item_app_ip_details.xml b/app/src/full/res/layout/list_item_app_ip_details.xml index d689b0810..7a1c2d1cf 100644 --- a/app/src/full/res/layout/list_item_app_ip_details.xml +++ b/app/src/full/res/layout/list_item_app_ip_details.xml @@ -90,7 +90,7 @@ android:background="@drawable/drawable_purple_gradient" android:fontFamily="sans-serif-light" android:gravity="center" - android:maxWidth="40dp" + android:maxWidth="60dp" android:minWidth="40dp" android:padding="5dp" android:singleLine="true" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8e2c486f1..423732226 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,8 +2,8 @@ + android:versionCode="44" + android:versionName="v055m"> diff --git a/app/src/main/java/com/celzero/bravedns/backup/BackupAgent.kt b/app/src/main/java/com/celzero/bravedns/backup/BackupAgent.kt index 98a48fc75..771a57b6a 100644 --- a/app/src/main/java/com/celzero/bravedns/backup/BackupAgent.kt +++ b/app/src/main/java/com/celzero/bravedns/backup/BackupAgent.kt @@ -130,7 +130,7 @@ class BackupAgent(val context: Context, workerParams: WorkerParameters) : return zipAndCopyToDestination(tempDir, backupFileUri) } catch (e: Exception) { - Logger.e( + Logger.crash( LOG_TAG_BACKUP_RESTORE, "exception during backup process, reason? ${e.message}", e @@ -166,7 +166,7 @@ class BackupAgent(val context: Context, workerParams: WorkerParameters) : filesPathToZip.add(metadataFile.absolutePath) return true } catch (e: Exception) { - Logger.e( + Logger.crash( LOG_TAG_BACKUP_RESTORE, "exception while creating meta data file, ${e.message}", e @@ -266,7 +266,7 @@ class BackupAgent(val context: Context, workerParams: WorkerParameters) : val allPrefs = sharedPrefs.all output.writeObject(allPrefs) } catch (e: Exception) { - Logger.e(LOG_TAG_BACKUP_RESTORE, "exception during shared pref backup, ${e.message}", e) + Logger.crash(LOG_TAG_BACKUP_RESTORE, "exception during shared pref backup, ${e.message}", e) return false } finally { try { diff --git a/app/src/main/java/com/celzero/bravedns/backup/RestoreAgent.kt b/app/src/main/java/com/celzero/bravedns/backup/RestoreAgent.kt index 243ad54b5..fdd39497f 100644 --- a/app/src/main/java/com/celzero/bravedns/backup/RestoreAgent.kt +++ b/app/src/main/java/com/celzero/bravedns/backup/RestoreAgent.kt @@ -133,7 +133,7 @@ class RestoreAgent(val context: Context, workerParams: WorkerParameters) : return true } catch (e: Exception) { - Logger.e( + Logger.crash( LOG_TAG_BACKUP_RESTORE, "exception during restore process, reason? ${e.message}", e @@ -257,7 +257,7 @@ class RestoreAgent(val context: Context, workerParams: WorkerParameters) : val metadata = stream.bufferedReader().use { it.readText() } isVersionSupported(metadata) } catch (ignored: Exception) { - Logger.e( + Logger.crash( LOG_TAG_BACKUP_RESTORE, "error while restoring metadata, reason? ${ignored.message}", ignored @@ -316,7 +316,7 @@ class RestoreAgent(val context: Context, workerParams: WorkerParameters) : } return false } catch (e: Exception) { - Logger.e( + Logger.crash( LOG_TAG_BACKUP_RESTORE, "exception while restoring shared pref, reason? ${e.message}", e @@ -340,7 +340,7 @@ class RestoreAgent(val context: Context, workerParams: WorkerParameters) : // there is only one database), so do not consider the backups prior to that return version >= minVersionSupported && persistentState.appVersion >= version } catch (e: Exception) { - Logger.e( + Logger.crash( LOG_TAG_BACKUP_RESTORE, "error while reading metadata, reason? ${e.message}", e @@ -379,7 +379,7 @@ class RestoreAgent(val context: Context, workerParams: WorkerParameters) : ) return true } catch (e: Exception) { - Logger.e( + Logger.crash( LOG_TAG_BACKUP_RESTORE, "exception while restoring shared pref, reason? ${e.message}", e 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 dca44134e..0a761d0cd 100644 --- a/app/src/main/java/com/celzero/bravedns/data/AppConfig.kt +++ b/app/src/main/java/com/celzero/bravedns/data/AppConfig.kt @@ -396,7 +396,11 @@ internal constructor( BraveMode.DNS.mode, BraveMode.DNS_FIREWALL.mode -> determineTunDnsMode() else -> { - Logger.e(LOG_TAG_VPN, "invalid brave mode: ${persistentState.braveMode}") + Logger.crash( + LOG_TAG_VPN, + "invalid brave mode: ${persistentState.braveMode}", + Exception() + ) TunDnsMode.NONE } } 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 7c0914911..01ce244e8 100644 --- a/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerDAO.kt +++ b/app/src/main/java/com/celzero/bravedns/database/ConnectionTrackerDAO.kt @@ -94,9 +94,9 @@ interface ConnectionTrackerDAO { 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 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 ipAddress, uid, port ORDER BY count DESC LIMIT 3" ) - fun getAppIpLogsLimited(uid: Int): PagingSource + 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" @@ -109,9 +109,9 @@ interface ConnectionTrackerDAO { fun getAppDomainLogs(uid: Int, to: Long): PagingSource @Query( - "SELECT uid, '' as ipAddress, port, COUNT(dnsQuery) as count, flag as flag, 0 as blocked, dnsQuery as appOrDnsName FROM ConnectionTracker WHERE uid = :uid and dnsQuery != '' GROUP BY dnsQuery ORDER BY count DESC LIMIT 3" + "SELECT uid, '' as ipAddress, port, COUNT(dnsQuery) as count, flag as flag, 0 as blocked, dnsQuery as appOrDnsName FROM ConnectionTracker WHERE uid = :uid and timeStamp > :to and dnsQuery != '' GROUP BY dnsQuery ORDER BY count DESC LIMIT 3" ) - fun getAppDomainLogsLimited(uid: Int): PagingSource + fun getAppDomainLogsLimited(uid: Int, 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 ConnectionTracker WHERE uid = :uid and timeStamp > :to and dnsQuery != '' and dnsQuery like :query GROUP BY dnsQuery ORDER BY count DESC" 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 8325a3f7f..5b49ed8ff 100644 --- a/app/src/main/java/com/celzero/bravedns/database/LogDatabase.kt +++ b/app/src/main/java/com/celzero/bravedns/database/LogDatabase.kt @@ -130,7 +130,7 @@ abstract class LogDatabase : RoomDatabase() { } db.enableWriteAheadLogging() } catch (ignored: Exception) { - Logger.e( + Logger.crash( "MIGRATION", "error migrating from v1to2 on log db: ${ignored.message}", ignored 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 7bebe7116..282924a66 100644 --- a/app/src/main/java/com/celzero/bravedns/database/RefreshDatabase.kt +++ b/app/src/main/java/com/celzero/bravedns/database/RefreshDatabase.kt @@ -195,7 +195,7 @@ internal constructor( // must be called after updateExistingPackagesIfNeeded refreshDomainRules(packagesToUpdate) } catch (e: RuntimeException) { - Logger.e(LOG_TAG_APP_DB, e.message ?: "refresh err", e) + Logger.crash(LOG_TAG_APP_DB, e.message ?: "refresh err", e) throw e } finally { notifyEmptyFirewallRulesIfNeeded() 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 6f793af4c..3796647a3 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 @@ -893,7 +893,7 @@ class GoVpnAdapter : KoinComponent { } } - private fun newDefaultTransport(url: String): intra.DefaultDNS { + private fun newDefaultTransport(url: String): intra.DefaultDNS? { val defaultDns = FALLBACK_DNS try { // when the url is empty, set the default transport to 8.8.4.4, 2001:4860:4860::8844 @@ -912,7 +912,12 @@ class GoVpnAdapter : KoinComponent { Logger.e(LOG_TAG_VPN, "err new default transport($url): ${e.message}", e) // most of the android devices have google dns, so add it as default transport // TODO: notify the user that the default transport could not be set - return Intra.newDefaultDNS(Backend.DNS53, defaultDns, "") + try { + return Intra.newDefaultDNS(Backend.DNS53, defaultDns, "") + } catch (e: Exception) { + Logger.crash(LOG_TAG_VPN, "err add $defaultDns transport: ${e.message}", e) + return null + } } } @@ -1138,7 +1143,7 @@ class GoVpnAdapter : KoinComponent { } return tunnel.resolver } catch (e: Exception) { - Logger.e(LOG_TAG_VPN, "err get resolver: ${e.message}", e) + Logger.crash(LOG_TAG_VPN, "err get resolver: ${e.message}", e) } return null } @@ -1151,7 +1156,7 @@ class GoVpnAdapter : KoinComponent { } return tunnel.resolver } catch (e: Exception) { - Logger.e(LOG_TAG_VPN, "err get resolver: ${e.message}", e) + Logger.crash(LOG_TAG_VPN, "err get resolver: ${e.message}", e) } return null } 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 6f430a220..628c8bd5b 100644 --- a/app/src/main/java/com/celzero/bravedns/service/BraveTileService.kt +++ b/app/src/main/java/com/celzero/bravedns/service/BraveTileService.kt @@ -36,7 +36,15 @@ class BraveTileService : TileService(), KoinComponent { private val persistentState by inject() override fun onCreate() { - persistentState.vpnEnabledLiveData.observeForever(this::updateTile) + // may be called multiple times, but we only need to observe once + // can't use onStartListening() as it is not called when the tile is added + // to the quick settings panel + super.onCreate() + try { + persistentState.vpnEnabledLiveData.observeForever(this::updateTile) + } catch (e: Exception) { + Logger.w(Logger.LOG_TAG_UI, "Tile: err in observing VPN state", e) + } } private fun updateTile(enabled: Boolean) { @@ -46,16 +54,36 @@ class BraveTileService : TileService(), KoinComponent { } } + override fun onStartListening() { + super.onStartListening() + try { + persistentState.vpnEnabledLiveData.observeForever(this::updateTile) + } catch (e: Exception) { + Logger.w(Logger.LOG_TAG_UI, "Tile: err in observing VPN state", e) + } + updateTile(persistentState.getVpnEnabled()) + } + + override fun onStopListening() { + super.onStopListening() + try { + persistentState.vpnEnabledLiveData.removeObserver(this::updateTile) + } catch (e: Exception) { + Logger.w(Logger.LOG_TAG_UI, "Tile: err in removing observer", e) + } + } + override fun onClick() { super.onClick() - - if (VpnController.isOn()) { + val isAppLockEnabled = persistentState.biometricAuth + // do not start or stop VPN if app lock is enabled + if (VpnController.isOn() && !isAppLockEnabled) { VpnController.stop(this) - } else if (VpnService.prepare(this) == null) { - // Start VPN service when VPN permission has been granted. + } else if (VpnService.prepare(this) == null && !isAppLockEnabled) { + // Start VPN service when VPN permission has been granted VpnController.start(this) } else { - // open Main activity when VPN permission has not been granted. + // open the app to handle the VPN start or stop val intent = if (Utilities.isHeadlessFlavour()) { Intent(this, PrepareVpnActivity::class.java) @@ -68,8 +96,9 @@ class BraveTileService : TileService(), KoinComponent { if (Utilities.isAtleastU()) { startActivityAndCollapse(pendingIntent) } else { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) - startActivityAndCollapse(intent) + // For older versions, convert PendingIntent to Intent and start the activity + val newIntent = Intent(intent).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + startActivity(newIntent) } } catch (e: UnsupportedOperationException) { // starting activity from TileService using an Intent is not allowed 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 316da32f5..1c2481896 100644 --- a/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt +++ b/app/src/main/java/com/celzero/bravedns/service/BraveVPNService.kt @@ -28,6 +28,7 @@ import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.app.Service +import android.app.UiModeManager import android.content.Context import android.content.Intent import android.content.SharedPreferences @@ -35,6 +36,7 @@ import android.content.SharedPreferences.OnSharedPreferenceChangeListener import android.content.pm.PackageManager import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_SYSTEM_EXEMPTED +import android.content.res.Configuration import android.net.ConnectivityManager import android.net.Network import android.net.NetworkCapabilities @@ -167,7 +169,8 @@ class BraveVPNService : } // handshake expiry time for proxy connections - private val wgHandshakeTimeout = TimeUnit.MINUTES.toMillis(3L) + // 120s (wireguard handshake) + 30s (router#status cache) + 10s buffer = 160s + private val wgHandshakeTimeout = TimeUnit.SECONDS.toMillis(160L) private val checkpointInterval = TimeUnit.MINUTES.toMillis(1L) private var isLockDownPrevious: Boolean = false @@ -275,11 +278,13 @@ class BraveVPNService : this.protect(fid.toInt()) + if (nws.isEmpty()) { + Logger.w(LOG_TAG_VPN, "no network to bind, who: $who, $addrPort") + return + } + var pfd: ParcelFileDescriptor? = null try { - - pfd = ParcelFileDescriptor.adoptFd(fid.toInt()) - // split the addrPort to get the IP address and convert it to InetAddress val dest = IpRulesManager.splitHostPort(addrPort) val destIp = IPAddressString(dest.first).address @@ -290,13 +295,14 @@ class BraveVPNService : // network with zero addresses if ( (destIp.isZero && who.startsWith(ProxyManager.ID_WG_BASE)) || - destIp.isAnyLocal || - destIp.isLoopback + destIp.isZero || destIp.isLoopback ) { logd("bind: invalid destIp: $destIp, who: $who, $addrPort") return } + pfd = ParcelFileDescriptor.adoptFd(fid.toInt()) + // check if the destination port is DNS port, if so bind to the network where the dns // belongs to, else bind to the available network val net = if (KnownPorts.isDns(destPort)) curnet?.dnsServers?.get(destAddr) else null @@ -323,7 +329,7 @@ class BraveVPNService : } finally { pfd?.detachFd() } - logd("bind: no network to bind, ${curnet?.dnsServers?.keys}, who: $who, $addrPort") + Logger.w( LOG_TAG_VPN, "bind failed: $who, $addrPort, $fid") } private fun bindToNw(net: Network, pfd: ParcelFileDescriptor): Boolean { @@ -405,22 +411,15 @@ class BraveVPNService : return FirewallRuleset.RULE8 } - val perAppDomainTentativeRule = getDomainRule(connInfo.query, uid) - val perAppIpTentativeRule = uidIpStatus(uid, connInfo.destIP, connInfo.destPort) - - when (perAppDomainTentativeRule) { + when (getDomainRule(connInfo.query, uid)) { DomainRulesManager.Status.BLOCK -> { logd("firewall: domain blocked, $uid") return FirewallRuleset.RULE2E } DomainRulesManager.Status.TRUST -> { - if (!perAppIpTentativeRule.isBlocked()) { - logd("firewall: domain trusted, $uid") - return FirewallRuleset.RULE2F - } else { - // fall-through, check ip rules - } + logd("firewall: domain trusted, $uid") + return FirewallRuleset.RULE2F } DomainRulesManager.Status.NONE -> { @@ -429,7 +428,7 @@ class BraveVPNService : } // IP rules - when (perAppIpTentativeRule) { + when (uidIpStatus(uid, connInfo.destIP, connInfo.destPort)) { IpRulesManager.IpRuleStatus.BLOCK -> { logd("firewall: ip blocked, $uid") return FirewallRuleset.RULE2 @@ -463,7 +462,6 @@ class BraveVPNService : } val globalDomainRule = getDomainRule(connInfo.query, UID_EVERYBODY) - val globalIpRule = globalIpRule(connInfo.destIP, connInfo.destPort) // should firewall rules by-pass universal firewall rules (previously whitelist) if (appStatus.bypassUniversal()) { @@ -486,12 +484,8 @@ class BraveVPNService : // check for global domain allow/block domains when (globalDomainRule) { DomainRulesManager.Status.TRUST -> { - if (!globalIpRule.isBlocked()) { - logd("firewall: global domain trusted, $uid, ${connInfo.query}") - return FirewallRuleset.RULE2I - } else { - // fall-through, check ip rules - } + logd("firewall: global domain trusted, $uid, ${connInfo.query}") + return FirewallRuleset.RULE2I } DomainRulesManager.Status.BLOCK -> { @@ -505,7 +499,7 @@ class BraveVPNService : } // should ip rules by-pass or block universal firewall rules - when (globalIpRule) { + when (globalIpRule(connInfo.destIP, connInfo.destPort)) { IpRulesManager.IpRuleStatus.BLOCK -> { logd("firewall: global ip blocked, $uid, ${connInfo.destIP}") return FirewallRuleset.RULE2D @@ -588,7 +582,7 @@ class BraveVPNService : } } catch (iex: Exception) { // TODO: show alerts to user on such exceptions, in a separate ui? - Logger.e(LOG_TAG_VPN, "err blocking conn, block anyway", iex) + Logger.crash(LOG_TAG_VPN, "unexpected err in firewall(), block anyway", iex) return FirewallRuleset.RULE1C } @@ -1281,11 +1275,18 @@ class BraveVPNService : // 1. Pause / Resume, Stop action button. // 2. RethinkDNS modes (dns & dns+firewall mode) // 3. No action button. - logd("notification action type: ${persistentState.notificationActionType}") + 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") - when ( - NotificationActionType.getNotificationActionType(persistentState.notificationActionType) - ) { + when (notifActionType) { NotificationActionType.PAUSE_STOP -> { // Add the action based on AppState (PAUSE/ACTIVE) val openIntent1 = @@ -1351,6 +1352,7 @@ class BraveVPNService : NotificationActionType.NONE -> { Logger.i(LOG_TAG_VPN, "No notification action") + builder.setContentTitle(contentTitle) } } @@ -1378,6 +1380,15 @@ class BraveVPNService : return notification } + private fun isAppRunningOnTv(): Boolean { + return try { + val uiModeManager: UiModeManager = getSystemService(UI_MODE_SERVICE) as UiModeManager + uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION + } catch (ignored: Exception) { + false + } + } + // keep in sync with RefreshDatabase#makeVpnIntent private fun makeVpnIntent(notificationID: Int, intentExtra: String): PendingIntent { val intent = Intent(this, NotificationActionReceiver::class.java) @@ -1735,6 +1746,11 @@ class BraveVPNService : notificationManager.notify(SERVICE_ID, updateNotificationBuilder()) } + PersistentState.BIOMETRIC_AUTH -> { + // update the notification builder to show the action buttons based on the biometric + notificationManager.notify(SERVICE_ID, updateNotificationBuilder()) + } + PersistentState.INTERNET_PROTOCOL -> { io("chooseIpVersion") { handleIPProtoChanges() } } @@ -2554,7 +2570,7 @@ class BraveVPNService : } return builder.establish() } catch (e: Exception) { - Logger.e(LOG_TAG_VPN, e.message ?: "err establishVpn", e) + Logger.crash(LOG_TAG_VPN, e.message ?: "err establishVpn", e) return null } } @@ -3532,19 +3548,21 @@ class BraveVPNService : logd("flow: skip refresh for $id, within interval: $cpIntervalSecs") return } + wgHandShakeCheckpoints[id] = realtime val lastHandShake = stats.lastOK - if (lastHandShake <= 0) { - Logger.w(LOG_TAG_VPN, "flow: skip refresh, handshake never done for $id") - return + 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 } - logd("flow: handshake check for $id, $lastHandShake, interval: $cpIntervalSecs") - wgHandShakeCheckpoints[id] = realtime - val currTimeMs = System.currentTimeMillis() - val durationMs = currTimeMs - lastHandShake - val durationSecs = TimeUnit.MILLISECONDS.toSeconds(durationMs) - // if the last handshake is older than the timeout, refresh the proxy - val mustRefresh = durationMs > wgHandshakeTimeout - Logger.i(LOG_TAG_VPN, "flow: refresh $id after $durationSecs: $mustRefresh") if (mustRefresh) { io("proxyHandshake") { vpnAdapter?.refreshProxy(id) } } diff --git a/app/src/main/java/com/celzero/bravedns/service/FirewallManager.kt b/app/src/main/java/com/celzero/bravedns/service/FirewallManager.kt index 8a6519c19..feef6e526 100644 --- a/app/src/main/java/com/celzero/bravedns/service/FirewallManager.kt +++ b/app/src/main/java/com/celzero/bravedns/service/FirewallManager.kt @@ -141,6 +141,10 @@ object FirewallManager : KoinComponent { return this == BYPASS_DNS_FIREWALL } + fun isExclude(): Boolean { + return this == EXCLUDE + } + fun isolate(): Boolean { return this == ISOLATE } 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 dde05b9e6..32ea508c2 100644 --- a/app/src/main/java/com/celzero/bravedns/service/PersistentState.kt +++ b/app/src/main/java/com/celzero/bravedns/service/PersistentState.kt @@ -58,6 +58,7 @@ class PersistentState(context: Context) : SimpleKrate(context), KoinComponent { const val CONNECTIVITY_CHECKS = "connectivity_check" const val NOTIFICATION_PERMISSION = "notification_permission_request" const val EXCLUDE_APPS_IN_PROXY = "exclude_apps_in_proxy" + const val BIOMETRIC_AUTH = "biometric_authentication" } // when vpn is started by the user, this is set to true; set to false when user stops 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 efcf8036d..348c718d4 100644 --- a/app/src/main/java/com/celzero/bravedns/service/RethinkBlocklistManager.kt +++ b/app/src/main/java/com/celzero/bravedns/service/RethinkBlocklistManager.kt @@ -273,7 +273,7 @@ object RethinkBlocklistManager : KoinComponent { Logger.i(LOG_TAG_DNS, "New Remote blocklist files inserted into database") return true } catch (ioException: IOException) { - Logger.e( + Logger.crash( LOG_TAG_DNS, "Failure reading json file, blocklist type: remote, timestamp: $timestamp", ioException 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 3a74363f4..3f03ec9a8 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 @@ -34,8 +34,7 @@ android:layout_alignParentStart="true" android:layout_centerVertical="true" android:layout_toStartOf="@id/one_wg_check" - android:orientation="vertical" - android:padding="5dp"> + android:orientation="vertical"> () + 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_BACKUP_RESTORE = "BackupRestore" + const val LOG_PROVIDER = "BlocklistProvider" + const val LOG_TAG_PROXY = "ProxyLogs" + const val LOG_QR_CODE = "QrCodeFromFileScanner" + + enum class LoggerType(val id: Int) { + VERY_VERBOSE(0), + VERBOSE(1), + DEBUG(2), + INFO(3), + WARN(4), + ERROR(5) + } + + 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) { + log(tag, message, LoggerType.ERROR, e) + if (Utilities.isPlayStoreFlavour()) { + try { + val crashlytics = FirebaseCrashlytics.getInstance() + crashlytics.log("$tag: $message") + crashlytics.recordException(e) + // 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 updateConfigLevel(level: Long) { + logLevel = level + } + + fun throwableToException(throwable: Throwable): Exception { + return if (throwable is Exception) { + throwable + } else { + Exception(throwable) + } + } + + private 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) + } + } +} diff --git a/app/src/website/java/com/celzero/bravedns/util/Logger.kt b/app/src/website/java/com/celzero/bravedns/util/Logger.kt new file mode 100644 index 000000000..21ab4e045 --- /dev/null +++ b/app/src/website/java/com/celzero/bravedns/util/Logger.kt @@ -0,0 +1,99 @@ +/* + * 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 org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +object Logger : KoinComponent { + private val persistentState 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_BACKUP_RESTORE = "BackupRestore" + const val LOG_PROVIDER = "BlocklistProvider" + const val LOG_TAG_PROXY = "ProxyLogs" + const val LOG_QR_CODE = "QrCodeFromFileScanner" + + enum class LoggerType(val id: Int) { + VERY_VERBOSE(0), + VERBOSE(1), + DEBUG(2), + INFO(3), + WARN(4), + ERROR(5) + } + + 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) { + log(tag, message, LoggerType.ERROR, e) + } + + fun updateConfigLevel(level: Long) { + logLevel = level + } + + fun throwableToException(throwable: Throwable): Exception { + return if (throwable is Exception) { + throwable + } else { + Exception(throwable) + } + } + + private 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) + } + } +} diff --git a/gradle.properties b/gradle.properties index 27e7adafc..6b86518fd 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 (43 for v055l) -VERSION_CODE=43 +# Version code for this module (44 for v055m) +VERSION_CODE=44