diff --git a/app/src/main/java/foundation/e/advancedprivacy/common/GraphHolder.kt b/app/src/main/java/foundation/e/advancedprivacy/common/GraphHolder.kt
deleted file mode 100644
index 44fb73d310162d814c41b04bed10a8016f447642..0000000000000000000000000000000000000000
--- a/app/src/main/java/foundation/e/advancedprivacy/common/GraphHolder.kt
+++ /dev/null
@@ -1,352 +0,0 @@
-/*
- * Copyright (C) 2021 E FOUNDATION
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-package foundation.e.advancedprivacy.common
-
-import android.content.Context
-import android.graphics.Canvas
-import android.text.Spannable
-import android.text.SpannableStringBuilder
-import android.text.style.DynamicDrawableSpan
-import android.text.style.ImageSpan
-import android.view.View
-import android.widget.TextView
-import androidx.core.content.ContextCompat
-import androidx.core.text.toSpannable
-import androidx.core.view.isVisible
-import com.github.mikephil.charting.charts.BarChart
-import com.github.mikephil.charting.components.AxisBase
-import com.github.mikephil.charting.components.MarkerView
-import com.github.mikephil.charting.components.XAxis
-import com.github.mikephil.charting.components.YAxis
-import com.github.mikephil.charting.components.YAxis.AxisDependency
-import com.github.mikephil.charting.data.BarData
-import com.github.mikephil.charting.data.BarDataSet
-import com.github.mikephil.charting.data.BarEntry
-import com.github.mikephil.charting.data.Entry
-import com.github.mikephil.charting.formatter.ValueFormatter
-import com.github.mikephil.charting.highlight.Highlight
-import com.github.mikephil.charting.listener.OnChartValueSelectedListener
-import com.github.mikephil.charting.renderer.XAxisRenderer
-import com.github.mikephil.charting.utils.MPPointF
-import foundation.e.advancedprivacy.R
-import foundation.e.advancedprivacy.common.extensions.dpToPxF
-import kotlin.math.floor
-
-class GraphHolder(val barChart: BarChart, val context: Context, val isMarkerAbove: Boolean = true) {
- var data = emptyList>()
- set(value) {
- field = value
- refreshDataSet()
- }
- var labels = emptyList()
-
- var graduations: List? = null
-
- private var isHighlighted = false
-
- init {
- barChart.description = null
- barChart.setTouchEnabled(true)
- barChart.setScaleEnabled(false)
-
- barChart.setDrawGridBackground(false)
- barChart.setDrawBorders(false)
- barChart.axisLeft.isEnabled = false
- barChart.axisRight.isEnabled = false
-
- barChart.legend.isEnabled = false
-
- if (isMarkerAbove) prepareXAxisDashboardDay() else prepareXAxisMarkersBelow()
-
- val periodMarker = PeriodMarkerView(context, isMarkerAbove)
- periodMarker.chartView = barChart
- barChart.marker = periodMarker
-
- barChart.setOnChartValueSelectedListener(object : OnChartValueSelectedListener {
- override fun onValueSelected(e: Entry?, h: Highlight?) {
- h?.let {
- val index = it.x.toInt()
- if (index >= 0 &&
- index < labels.size &&
- index < this@GraphHolder.data.size
- ) {
- val period = labels[index]
- val (blocked, leaked) = this@GraphHolder.data[index]
- periodMarker.setLabel(period, blocked, leaked)
- }
- }
- isHighlighted = true
- }
-
- override fun onNothingSelected() {
- isHighlighted = false
- }
- })
- }
-
- private fun prepareXAxisDashboardDay() {
- barChart.extraTopOffset = 44f
-
- barChart.offsetTopAndBottom(0)
-
- barChart.setXAxisRenderer(object : XAxisRenderer(
- barChart.viewPortHandler,
- barChart.xAxis,
- barChart.getTransformer(AxisDependency.LEFT)
- ) {
- override fun renderAxisLine(c: Canvas) {
- mAxisLinePaint.color = mXAxis.axisLineColor
- mAxisLinePaint.strokeWidth = mXAxis.axisLineWidth
- mAxisLinePaint.pathEffect = mXAxis.axisLineDashPathEffect
-
- // Top line
- c.drawLine(
- mViewPortHandler.contentLeft(),
- mViewPortHandler.contentTop(),
- mViewPortHandler.contentRight(),
- mViewPortHandler.contentTop(),
- mAxisLinePaint
- )
-
- // Bottom line
- c.drawLine(
- mViewPortHandler.contentLeft(),
- mViewPortHandler.contentBottom() - 7.dpToPxF(context),
- mViewPortHandler.contentRight(),
- mViewPortHandler.contentBottom() - 7.dpToPxF(context),
- mAxisLinePaint
- )
- }
-
- override fun renderGridLines(c: Canvas) {
- if (!mXAxis.isDrawGridLinesEnabled || !mXAxis.isEnabled) return
- val clipRestoreCount = c.save()
- c.clipRect(gridClippingRect)
- if (mRenderGridLinesBuffer.size != mAxis.mEntryCount * 2) {
- mRenderGridLinesBuffer = FloatArray(mXAxis.mEntryCount * 2)
- }
- val positions = mRenderGridLinesBuffer
- run {
- var i = 0
- while (i < positions.size) {
- positions[i] = mXAxis.mEntries[i / 2]
- positions[i + 1] = mXAxis.mEntries[i / 2]
- i += 2
- }
- }
-
- mTrans.pointValuesToPixel(positions)
- setupGridPaint()
- val gridLinePath = mRenderGridLinesPath
- gridLinePath.reset()
- var i = 0
- while (i < positions.size) {
- val bottomY = if (graduations?.getOrNull(i / 2) != null) 0 else 3
- val x = positions[i]
- gridLinePath.moveTo(x, mViewPortHandler.contentBottom() - 7.dpToPxF(context))
- gridLinePath.lineTo(x, mViewPortHandler.contentBottom() - bottomY.dpToPxF(context))
-
- c.drawPath(gridLinePath, mGridPaint)
-
- gridLinePath.reset()
-
- i += 2
- }
- c.restoreToCount(clipRestoreCount)
- }
- })
-
- barChart.setDrawValueAboveBar(false)
- barChart.xAxis.apply {
- isEnabled = true
- position = XAxis.XAxisPosition.BOTTOM
-
- setDrawGridLines(true)
- setDrawLabels(true)
- setCenterAxisLabels(false)
- setLabelCount(25, true)
- textColor = context.getColor(R.color.primary_text)
- valueFormatter = object : ValueFormatter() {
- override fun getAxisLabel(value: Float, axis: AxisBase?): String {
- return graduations?.getOrNull(floor(value).toInt() + 1) ?: ""
- }
- }
- }
- }
-
- private fun prepareXAxisMarkersBelow() {
- barChart.extraBottomOffset = 44f
-
- barChart.offsetTopAndBottom(0)
- barChart.setDrawValueAboveBar(false)
-
- barChart.xAxis.apply {
- isEnabled = true
- position = XAxis.XAxisPosition.BOTH_SIDED
- setDrawGridLines(false)
- setDrawLabels(false)
- }
- }
-
- fun highlightIndex(index: Int) {
- if (index >= 0 && index < data.size) {
- val xPx = barChart.getTransformer(YAxis.AxisDependency.LEFT)
- .getPixelForValues(index.toFloat(), 0f)
- .x
- val highlight = Highlight(
- index.toFloat(),
- 0f,
- xPx.toFloat(),
- 0f,
- 0,
- YAxis.AxisDependency.LEFT
- )
-
- barChart.highlightValue(highlight, true)
- }
- }
-
- private fun refreshDataSet() {
- val trackersDataSet = BarDataSet(
- data.mapIndexed { index, value ->
- BarEntry(
- index.toFloat(),
- floatArrayOf(value.first.toFloat(), value.second.toFloat())
- )
- },
- ""
- ).apply {
- val blockedColor = ContextCompat.getColor(context, R.color.accent)
- val leakedColor = ContextCompat.getColor(context, R.color.red_off)
-
- colors = listOf(
- blockedColor,
- leakedColor
- )
-
- setDrawValues(false)
- }
-
- barChart.data = BarData(trackersDataSet)
- barChart.invalidate()
- }
-}
-
-class PeriodMarkerView(context: Context, private val isMarkerAbove: Boolean = true) : MarkerView(context, R.layout.chart_tooltip) {
- enum class ArrowPosition { LEFT, CENTER, RIGHT }
-
- private val arrowMargins = 10.dpToPxF(context)
- private val mOffset2 = MPPointF(0f, 0f)
-
- private fun getArrowPosition(posX: Float): ArrowPosition {
- val halfWidth = width / 2
-
- return chartView?.let { chart ->
- if (posX < halfWidth) {
- ArrowPosition.LEFT
- } else if (chart.width - posX < halfWidth) {
- ArrowPosition.RIGHT
- } else {
- ArrowPosition.CENTER
- }
- } ?: ArrowPosition.CENTER
- }
-
- private fun showArrow(position: ArrowPosition?) {
- val ids = listOf(
- R.id.arrow_top_left,
- R.id.arrow_top_center,
- R.id.arrow_top_right,
- R.id.arrow_bottom_left,
- R.id.arrow_bottom_center,
- R.id.arrow_bottom_right
- )
-
- val toShow = if (isMarkerAbove) {
- when (position) {
- ArrowPosition.LEFT -> R.id.arrow_bottom_left
- ArrowPosition.CENTER -> R.id.arrow_bottom_center
- ArrowPosition.RIGHT -> R.id.arrow_bottom_right
- else -> null
- }
- } else {
- when (position) {
- ArrowPosition.LEFT -> R.id.arrow_top_left
- ArrowPosition.CENTER -> R.id.arrow_top_center
- ArrowPosition.RIGHT -> R.id.arrow_top_right
- else -> null
- }
- }
-
- ids.forEach { id ->
- val showIt = id == toShow
- findViewById(id)?.let {
- if (it.isVisible != showIt) {
- it.isVisible = showIt
- }
- }
- }
- }
-
- fun setLabel(period: String, blocked: Int, leaked: Int) {
- val span = SpannableStringBuilder(period)
- span.append(": $blocked ")
- span.setSpan(
- ImageSpan(context, R.drawable.ic_legend_blocked, DynamicDrawableSpan.ALIGN_BASELINE),
- span.length - 1,
- span.length,
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
- )
- span.append(" $leaked ")
- span.setSpan(
- ImageSpan(context, R.drawable.ic_legend_leaked, DynamicDrawableSpan.ALIGN_BASELINE),
- span.length - 1,
- span.length,
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
- )
- findViewById(R.id.label).text = span.toSpannable()
- }
-
- override fun refreshContent(e: Entry?, highlight: Highlight?) {
- highlight?.let {
- showArrow(getArrowPosition(highlight.xPx))
- }
- super.refreshContent(e, highlight)
- }
-
- override fun getOffsetForDrawingAtPoint(posX: Float, posY: Float): MPPointF {
- val x = when (getArrowPosition(posX)) {
- ArrowPosition.LEFT -> -arrowMargins
- ArrowPosition.RIGHT -> -width + arrowMargins
- ArrowPosition.CENTER -> -width.toFloat() / 2
- }
-
- mOffset2.x = x
- mOffset2.y = if (isMarkerAbove) {
- -posY
- } else {
- -posY + (chartView?.height?.toFloat() ?: 0f) - height
- }
-
- return mOffset2
- }
-
- override fun draw(canvas: Canvas?, posX: Float, posY: Float) {
- super.draw(canvas, posX, posY)
- }
-}
diff --git a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt
index 64520a502ab7565d23956d4645a6fa67bc2628da..374afe661f3d750a604b0c029465ec8522c7690c 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/domain/usecases/TrackersStatisticsUseCase.kt
@@ -35,8 +35,6 @@ import java.time.temporal.ChronoUnit
import kotlin.time.Duration
import kotlin.time.Duration.Companion.seconds
import kotlinx.coroutines.FlowPreview
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onStart
@@ -57,38 +55,6 @@ class TrackersStatisticsUseCase(
.throttleFirst(windowDuration = debounce)
.onStart { emit(Unit) }
- fun getDayStatistics(): Pair {
- return TrackersPeriodicStatistics(
- callsBlockedNLeaked = statisticsUseCase.getTrackersCallsOnPeriod(24, ChronoUnit.HOURS),
- periods = buildDayLabels(),
- trackersCount = statisticsUseCase.getActiveTrackersByPeriod(24, ChronoUnit.HOURS),
- graduations = buildDayGraduations()
- ) to statisticsUseCase.getContactedTrackersCount()
- }
-
- fun getNonBlockedTrackersCount(): Flow {
- return if (whitelistRepository.isBlockingEnabled) {
- appListsRepository.allApps().map { apps ->
- val whiteListedTrackers = mutableSetOf()
- val whiteListedApps = whitelistRepository.getWhiteListedApp()
- apps.forEach { app ->
- if (app in whiteListedApps) {
- whiteListedTrackers.addAll(statisticsUseCase.getTrackers(listOf(app)))
- } else {
- whiteListedTrackers.addAll(getWhiteList(app))
- }
- }
- whiteListedTrackers.size
- }
- } else {
- flowOf(statisticsUseCase.getContactedTrackersCount())
- }
- }
-
- fun getMostLeakedApp(): ApplicationDescription? {
- return statisticsUseCase.getMostLeakedApp(24, ChronoUnit.HOURS)
- }
-
fun getDayTrackersCalls() = statisticsUseCase.getTrackersCallsOnPeriod(24, ChronoUnit.HOURS)
fun getDayTrackersCount() = statisticsUseCase.getActiveTrackersByPeriod(24, ChronoUnit.HOURS)
@@ -211,18 +177,12 @@ class TrackersStatisticsUseCase(
)
}
- suspend fun getCalls(app: ApplicationDescription): Pair {
- return appListsRepository.mapReduceForHiddenApps(
- app = app,
- map = {
- statisticsUseCase.getCalls(it, 24, ChronoUnit.HOURS)
- },
- reduce = { zip ->
- zip.unzip().let { (blocked, leaked) ->
- blocked.sum() to leaked.sum()
- }
- }
- )
+ suspend fun getLastMonthBlockedLeaksCount(): Int {
+ return statsDatabase.getBlockedLeaksCount(Period.MONTH.periodsCount, Period.MONTH.periodUnit)
+ }
+
+ suspend fun getLastMonthAppsWithBLockedLeaksCount(): Int {
+ return statsDatabase.getAppsWithBLockedLeaksCount(Period.MONTH.periodsCount, Period.MONTH.periodUnit)
}
private fun getWhiteList(app: ApplicationDescription): List {
diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt
index 31c4d07cf8c480ec7389335bca63cd81687e758f..9cdffa45859bf95be574e0ad52ef4b05d61db00f 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardFragment.kt
@@ -19,26 +19,20 @@
package foundation.e.advancedprivacy.features.dashboard
import android.os.Bundle
-import android.text.Html
-import android.text.Html.FROM_HTML_MODE_LEGACY
import android.view.View
import android.widget.Toast
+import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat.getColor
import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.fragment.findNavController
-import androidx.navigation.fragment.navArgs
import foundation.e.advancedprivacy.R
-import foundation.e.advancedprivacy.common.GraphHolder
import foundation.e.advancedprivacy.common.NavToolbarFragment
import foundation.e.advancedprivacy.databinding.FragmentDashboardBinding
import foundation.e.advancedprivacy.domain.entities.FeatureState
-import foundation.e.advancedprivacy.domain.entities.LocationMode
-import foundation.e.advancedprivacy.domain.entities.QuickPrivacyState
import foundation.e.advancedprivacy.domain.entities.TrackerMode
-import foundation.e.advancedprivacy.features.dashboard.DashboardViewModel.Action
import foundation.e.advancedprivacy.features.dashboard.DashboardViewModel.SingleEvent
import kotlinx.coroutines.launch
import org.koin.androidx.viewmodel.ext.android.viewModel
@@ -46,64 +40,89 @@ import org.koin.androidx.viewmodel.ext.android.viewModel
class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) {
private val viewModel: DashboardViewModel by viewModel()
- private var graphHolder: GraphHolder? = null
-
- private var _binding: FragmentDashboardBinding? = null
- private val binding get() = _binding!!
-
- private var highlightIndexOnStart: Int? = null
-
- private val args: DashboardFragmentArgs by navArgs()
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- highlightIndexOnStart = args.highlightLeaks
- }
+ private lateinit var binding: FragmentDashboardBinding
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
- _binding = FragmentDashboardBinding.bind(view)
-
- graphHolder = GraphHolder(binding.graph, requireContext())
-
- binding.leakingAppButton.setOnClickListener {
- viewModel.submitAction(Action.ShowMostLeakedApp)
- }
- binding.toggleTrackers.setOnClickListener {
- viewModel.submitAction(
- Action.ToggleTrackers(
- enabled = binding.toggleTrackers.isChecked
+ binding = FragmentDashboardBinding.bind(view)
+
+ with(binding.dataBlockedTrackers) {
+ primaryMessage.apply {
+ setText(R.string.dashboard_data_blocked_trackers_primary)
+ setCompoundDrawables(
+ ContextCompat.getDrawable(requireContext(), R.drawable.ic_block_24),
+ null,
+ null,
+ null
)
- )
+ }
+
+ secondaryMessage.setText(R.string.dashboard_data_blocked_trackers_secondary)
}
- binding.toggleLocation.setOnClickListener {
- viewModel.submitAction(
- Action.ToggleLocation(
- enabled = binding.toggleLocation.isChecked
+ with(binding.dataApps) {
+ primaryMessage.apply {
+ setText(R.string.dashboard_data_apps_primary)
+ setCompoundDrawables(
+ ContextCompat.getDrawable(requireContext(), R.drawable.ic_apps_24),
+ null,
+ null,
+ null
)
- )
+ }
+
+ secondaryMessage.setText(R.string.dashboard_data_apps_secondary)
}
- binding.toggleIpscrambling.setOnClickListener {
- viewModel.submitAction(
- Action.ToggleIpScrambling(
- enabled = binding.toggleIpscrambling.isChecked
- )
- )
+
+ binding.trackersControl.title.setText(R.string.dashboard_trackers_title)
+ binding.fakeLocation.title.setText(R.string.dashboard_location_title)
+ binding.ipScrambling.title.setText(R.string.dashboard_ipscrambling_title)
+
+ setOnClickListeners()
+
+ listenViewModel()
+ }
+
+ private fun setOnClickListeners() {
+ binding.viewTrackersStatistics.setOnClickListener {
+ viewModel.onClickViewTrackersStatistics()
}
- binding.myLocation.container.setOnClickListener {
- viewModel.submitAction(Action.ShowFakeMyLocationAction)
+
+ with(binding.trackersControl) {
+ root.setOnClickListener {
+ viewModel.onClickTrackersControl()
+ }
+
+ switchFeature.setOnCheckedChangeListener { _, isChecked ->
+ viewModel.onClickToggleTrackersContol(isChecked)
+ }
}
- binding.internetActivityPrivacy.container.setOnClickListener {
- viewModel.submitAction(Action.ShowInternetActivityPrivacyAction)
+
+ with(binding.fakeLocation) {
+ root.setOnClickListener {
+ viewModel.onClickFakeLocation()
+ }
+
+ switchFeature.setOnCheckedChangeListener { _, isChecked ->
+ viewModel.onClickToggleFakeLocation(isChecked)
+ }
}
- binding.appsPermissions.container.setOnClickListener {
- viewModel.submitAction(Action.ShowAppsPermissions)
+
+ with(binding.ipScrambling) {
+ root.setOnClickListener {
+ viewModel.onClickIpScrambling()
+ }
+
+ switchFeature.setOnCheckedChangeListener { _, isChecked ->
+ viewModel.onClickToggleIpScrambling(isChecked)
+ }
}
- binding.amITracked.container.setOnClickListener {
- viewModel.submitAction(Action.ShowTrackers)
+ binding.appsPermissions.setOnClickListener {
+ viewModel.onClickAppsPermissions()
}
+ }
+ private fun listenViewModel() {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
render(viewModel.state.value)
@@ -125,6 +144,7 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) {
}
}
}
+
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.navigate.collect(findNavController()::navigate)
@@ -144,152 +164,63 @@ class DashboardFragment : NavToolbarFragment(R.layout.fragment_dashboard) {
}
private fun render(state: DashboardState) {
- binding.stateLabel.text = getString(
- when (state.quickPrivacyState) {
- QuickPrivacyState.DISABLED -> R.string.dashboard_state_title_off
- QuickPrivacyState.FULL_ENABLED -> R.string.dashboard_state_title_on
- QuickPrivacyState.ENABLED -> R.string.dashboard_state_title_custom
- }
- )
-
- binding.stateIcon.setImageResource(
- if (state.quickPrivacyState.isEnabled()) {
- R.drawable.ic_shield_on
- } else {
- R.drawable.ic_shield_off
- }
- )
+ binding.dataBlockedTrackers.number.text = state.blockedCallsCount.toString()
+ binding.dataApps.number.text = state.appsWithCallsCount.toString()
- binding.toggleTrackers.isChecked = state.trackerMode != TrackerMode.VULNERABLE
+ with(binding.trackersControl) {
+ switchFeature.isChecked = state.trackerMode != TrackerMode.VULNERABLE
- binding.stateTrackers.text = getString(
- when (state.trackerMode) {
- TrackerMode.DENIED -> R.string.dashboard_state_trackers_on
- TrackerMode.VULNERABLE -> R.string.dashboard_state_trackers_off
- TrackerMode.CUSTOM -> R.string.dashboard_state_trackers_custom
- }
- )
- binding.stateTrackers.setTextColor(
- getColor(
- requireContext(),
- if (state.trackerMode == TrackerMode.VULNERABLE) {
- R.color.red_off
- } else {
- R.color.green_valid
+ stateLabel.setText(
+ when (state.trackerMode) {
+ TrackerMode.DENIED -> R.string.dashboard_state_trackers_on
+ TrackerMode.VULNERABLE -> R.string.dashboard_state_trackers_off
+ TrackerMode.CUSTOM -> R.string.dashboard_state_trackers_custom
}
)
- )
- binding.toggleLocation.isChecked = state.isLocationHidden
+ stateLabel.setTextColor(getStateColor(state.trackerMode != TrackerMode.VULNERABLE))
+ }
- binding.stateGeolocation.text = getString(
- if (state.isLocationHidden) {
- R.string.dashboard_state_geolocation_on
- } else {
- R.string.dashboard_state_geolocation_off
- }
- )
- binding.stateGeolocation.setTextColor(
- getColor(
- requireContext(),
+ with(binding.fakeLocation) {
+ switchFeature.isChecked = state.isLocationHidden
+ stateLabel.setText(
if (state.isLocationHidden) {
- R.color.green_valid
+ R.string.dashboard_state_geolocation_on
} else {
- R.color.red_off
+ R.string.dashboard_state_geolocation_off
}
)
- )
+ stateLabel.setTextColor(getStateColor(state.isLocationHidden))
+ }
- binding.toggleIpscrambling.isChecked = state.ipScramblingMode.isChecked
- val isLoading = state.ipScramblingMode.isLoading
- binding.toggleIpscrambling.isEnabled = (
- state.ipScramblingMode != FeatureState.STOPPING
- )
+ with(binding.ipScrambling) {
+ switchFeature.isChecked = state.ipScramblingMode.isChecked
- binding.stateIpAddress.text = getString(
- if (state.ipScramblingMode == FeatureState.ON) {
- R.string.dashboard_state_ipaddress_on
- } else {
- R.string.dashboard_state_ipaddress_off
- }
- )
+ val isLoading = state.ipScramblingMode.isLoading
+ switchFeature.isEnabled = (state.ipScramblingMode != FeatureState.STOPPING)
- binding.stateIpAddressLoader.visibility = if (isLoading) View.VISIBLE else View.GONE
- binding.stateIpAddress.visibility = if (!isLoading) View.VISIBLE else View.GONE
+ stateLoader.isVisible = isLoading
+ stateLabel.visibility = if (!isLoading) View.VISIBLE else View.INVISIBLE
- binding.stateIpAddress.setTextColor(
- getColor(
- requireContext(),
+ stateLabel.setText(
if (state.ipScramblingMode == FeatureState.ON) {
- R.color.green_valid
+ R.string.dashboard_state_ipaddress_on
} else {
- R.color.red_off
+ R.string.dashboard_state_ipaddress_off
}
)
- )
-
- if (state.dayStatistics?.all { it.first == 0 && it.second == 0 } == true) {
- binding.graph.visibility = View.INVISIBLE
- binding.graphLegend.isVisible = false
- binding.leakingAppButton.isVisible = false
- binding.graphEmpty.isVisible = true
- } else {
- binding.graph.isVisible = true
- binding.graphLegend.isVisible = true
- binding.leakingAppButton.isVisible = true
- binding.graphEmpty.isVisible = false
- state.dayStatistics?.let { graphHolder?.data = it }
- state.dayLabels?.let { graphHolder?.labels = it }
- state.dayGraduations?.let { graphHolder?.graduations = it }
-
- binding.graphLegend.text = Html.fromHtml(
- getString(
- R.string.dashboard_graph_trackers_legend,
- state.leakedTrackersCount?.toString() ?: "No"
- ),
- FROM_HTML_MODE_LEGACY
- )
-
- highlightIndexOnStart?.let {
- binding.graph.post {
- graphHolder?.highlightIndex(it)
- }
- highlightIndexOnStart = null
- }
+ stateLabel.setTextColor(getStateColor(state.ipScramblingMode == FeatureState.ON))
}
+ }
- if (state.allowedTrackersCount != null && state.trackersCount != null) {
- binding.amITracked.subTitle = getString(
- R.string.dashboard_am_i_tracked_subtitle,
- state.trackersCount,
- state.allowedTrackersCount
- )
- } else {
- binding.amITracked.subTitle = ""
- }
-
- binding.myLocation.subTitle = getString(
- when (state.locationMode) {
- LocationMode.REAL_LOCATION -> R.string.dashboard_location_subtitle_off
- LocationMode.SPECIFIC_LOCATION -> R.string.dashboard_location_subtitle_specific
- LocationMode.RANDOM_LOCATION -> R.string.dashboard_location_subtitle_random
- }
- )
-
- binding.internetActivityPrivacy.subTitle = getString(
- if (state.ipScramblingMode == FeatureState.ON) {
- R.string.dashboard_internet_activity_privacy_subtitle_on
+ private fun getStateColor(isActive: Boolean): Int {
+ return getColor(
+ requireContext(),
+ if (isActive) {
+ R.color.green_valid
} else {
- R.string.dashboard_internet_activity_privacy_subtitle_off
+ R.color.red_off
}
)
-
- binding.executePendingBindings()
- }
-
- override fun onDestroyView() {
- super.onDestroyView()
- graphHolder = null
- _binding = null
}
}
diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardState.kt b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardState.kt
index 774901a8ef4d44d722ca463b965037b5e81280cf..e7b39b6457b46085f5e7810fc72c9b555b15ccd6 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardState.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardState.kt
@@ -18,7 +18,6 @@
package foundation.e.advancedprivacy.features.dashboard
import foundation.e.advancedprivacy.domain.entities.FeatureState
-import foundation.e.advancedprivacy.domain.entities.LocationMode
import foundation.e.advancedprivacy.domain.entities.QuickPrivacyState
import foundation.e.advancedprivacy.domain.entities.TrackerMode
@@ -27,11 +26,6 @@ data class DashboardState(
val trackerMode: TrackerMode = TrackerMode.VULNERABLE,
val isLocationHidden: Boolean = false,
val ipScramblingMode: FeatureState = FeatureState.STOPPING,
- val locationMode: LocationMode = LocationMode.REAL_LOCATION,
- val leakedTrackersCount: Int? = null,
- val trackersCount: Int? = null,
- val allowedTrackersCount: Int? = null,
- val dayStatistics: List>? = null,
- val dayLabels: List? = null,
- val dayGraduations: List? = null
+ val blockedCallsCount: Int = 0,
+ val appsWithCallsCount: Int = 0
)
diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardViewModel.kt b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardViewModel.kt
index a36b24a3bcb24f59f4414806723597e0616cf72e..a820be6576114cb05071c15a6ddd4e9cf53add22 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardViewModel.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/features/dashboard/DashboardViewModel.kt
@@ -26,15 +26,12 @@ import foundation.e.advancedprivacy.R
import foundation.e.advancedprivacy.domain.usecases.GetQuickPrivacyStateUseCase
import foundation.e.advancedprivacy.domain.usecases.TrackersStatisticsUseCase
import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.first
-import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.flow.merge
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@@ -59,24 +56,20 @@ class DashboardViewModel(
suspend fun doOnStartedState() = withContext(Dispatchers.IO) {
merge(
- getPrivacyStateUseCase.quickPrivacyState.map {
- _state.update { s -> s.copy(quickPrivacyState = it) }
- },
getPrivacyStateUseCase.ipScramblingMode.map {
_state.update { s -> s.copy(ipScramblingMode = it) }
},
- trackersStatisticsUseCase.listenUpdates().flatMapLatest {
+
+ trackersStatisticsUseCase.listenUpdates().mapLatest {
fetchStatistics()
},
+
getPrivacyStateUseCase.trackerMode.map {
_state.update { s -> s.copy(trackerMode = it) }
},
getPrivacyStateUseCase.isLocationHidden.map {
_state.update { s -> s.copy(isLocationHidden = it) }
},
- getPrivacyStateUseCase.locationMode.map {
- _state.update { s -> s.copy(locationMode = it) }
- },
getPrivacyStateUseCase.otherVpnRunning.map {
_singleEvents.emit(
SingleEvent.ToastMessageSingleEvent(
@@ -88,52 +81,49 @@ class DashboardViewModel(
).collect {}
}
- fun submitAction(action: Action) = viewModelScope.launch {
- when (action) {
- is Action.ToggleTrackers -> {
- getPrivacyStateUseCase.toggleTrackers(action.enabled)
- // Add delay here to prevent race condition with trackers state.
- delay(200)
- fetchStatistics().first()
- }
- is Action.ToggleLocation -> getPrivacyStateUseCase.toggleLocation(action.enabled)
- is Action.ToggleIpScrambling ->
- getPrivacyStateUseCase.toggleIpScrambling(action.enabled)
- is Action.ShowFakeMyLocationAction ->
- _navigate.emit(DashboardFragmentDirections.gotoFakeLocationFragment())
- is Action.ShowAppsPermissions ->
- _navigate.emit(DashboardFragmentDirections.gotoSettingsPermissionsActivity())
- is Action.ShowInternetActivityPrivacyAction ->
- _navigate.emit(DashboardFragmentDirections.gotoInternetPrivacyFragment())
- is Action.ShowTrackers ->
- _navigate.emit(DashboardFragmentDirections.gotoTrackersFragment())
- is Action.ShowMostLeakedApp -> actionShowMostLeakedApp()
- }
+ fun onClickViewTrackersStatistics() = viewModelScope.launch {
+ _navigate.emit(DashboardFragmentDirections.gotoTrackersFragment())
}
- private suspend fun fetchStatistics(): Flow = withContext(Dispatchers.IO) {
- trackersStatisticsUseCase.getNonBlockedTrackersCount().map { nonBlockedTrackersCount ->
- trackersStatisticsUseCase.getDayStatistics().let { (dayStatistics, trackersCount) ->
- _state.update { s ->
- s.copy(
- dayStatistics = dayStatistics.callsBlockedNLeaked,
- dayLabels = dayStatistics.periods,
- dayGraduations = dayStatistics.graduations,
- leakedTrackersCount = dayStatistics.trackersCount,
- trackersCount = trackersCount,
- allowedTrackersCount = nonBlockedTrackersCount
- )
- }
- }
- }
+ fun onClickTrackersControl() = viewModelScope.launch {
+ _navigate.emit(DashboardFragmentDirections.gotoTrackersFragment())
+ }
+
+ fun onClickToggleTrackersContol(enabled: Boolean) = viewModelScope.launch(Dispatchers.IO) {
+ getPrivacyStateUseCase.toggleTrackers(enabled)
+ }
+
+ fun onClickFakeLocation() = viewModelScope.launch {
+ _navigate.emit(DashboardFragmentDirections.gotoFakeLocationFragment())
}
- private suspend fun actionShowMostLeakedApp() = withContext(Dispatchers.IO) {
- _navigate.emit(
- trackersStatisticsUseCase.getMostLeakedApp()?.let {
- DashboardFragmentDirections.gotoAppTrackersFragment(appUid = it.uid)
- } ?: DashboardFragmentDirections.gotoTrackersFragment()
- )
+ fun onClickToggleFakeLocation(enabled: Boolean) = viewModelScope.launch(Dispatchers.IO) {
+ getPrivacyStateUseCase.toggleLocation(enabled)
+ }
+
+ fun onClickIpScrambling() = viewModelScope.launch {
+ _navigate.emit(DashboardFragmentDirections.gotoInternetPrivacyFragment())
+ }
+
+ fun onClickToggleIpScrambling(enabled: Boolean) = viewModelScope.launch(Dispatchers.IO) {
+ getPrivacyStateUseCase.toggleIpScrambling(enabled)
+ }
+
+ fun onClickAppsPermissions() = viewModelScope.launch {
+ _navigate.emit(DashboardFragmentDirections.gotoSettingsPermissionsActivity())
+ }
+
+ private suspend fun fetchStatistics() = withContext(Dispatchers.IO) {
+ val blockedCallsCount = trackersStatisticsUseCase.getLastMonthBlockedLeaksCount()
+
+ val appsWithBlockedLeaksCount = trackersStatisticsUseCase.getLastMonthAppsWithBLockedLeaksCount()
+
+ _state.update {
+ it.copy(
+ blockedCallsCount = blockedCallsCount,
+ appsWithCallsCount = appsWithBlockedLeaksCount
+ )
+ }
}
sealed class SingleEvent {
@@ -142,15 +132,4 @@ class DashboardViewModel(
val args: List = emptyList()
) : SingleEvent()
}
-
- sealed class Action {
- data class ToggleTrackers(val enabled: Boolean) : Action()
- data class ToggleLocation(val enabled: Boolean) : Action()
- data class ToggleIpScrambling(val enabled: Boolean) : Action()
- object ShowFakeMyLocationAction : Action()
- object ShowInternetActivityPrivacyAction : Action()
- object ShowAppsPermissions : Action()
- object ShowTrackers : Action()
- object ShowMostLeakedApp : Action()
- }
}
diff --git a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/graph/PeriodMarkerView.kt b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/graph/PeriodMarkerView.kt
index be7e81bc5521255790a137dd1e07b3cf75e83b06..eb651beef7a3d6def03fa34607a056c0bcaff28d 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/features/trackers/graph/PeriodMarkerView.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/features/trackers/graph/PeriodMarkerView.kt
@@ -34,7 +34,7 @@ import com.github.mikephil.charting.utils.MPPointF
import foundation.e.advancedprivacy.R
import foundation.e.advancedprivacy.common.extensions.dpToPxF
-class PeriodMarkerView(context: Context) : MarkerView(context, R.layout.chart_tooltip_2) {
+class PeriodMarkerView(context: Context) : MarkerView(context, R.layout.chart_tooltip) {
enum class ArrowPosition { LEFT, CENTER, RIGHT }
private val arrowMargins = 10.dpToPxF(context)
@@ -56,9 +56,6 @@ class PeriodMarkerView(context: Context) : MarkerView(context, R.layout.chart_to
private fun showArrow(position: ArrowPosition?) {
val ids = listOf(
- R.id.arrow_top_left,
- R.id.arrow_top_center,
- R.id.arrow_top_right,
R.id.arrow_bottom_left,
R.id.arrow_bottom_center,
R.id.arrow_bottom_right
@@ -85,7 +82,7 @@ class PeriodMarkerView(context: Context) : MarkerView(context, R.layout.chart_to
val span = SpannableStringBuilder(period)
span.append(" | ")
span.setSpan(
- ImageSpan(context, R.drawable.ic_legend_blocked_2, DynamicDrawableSpan.ALIGN_BASELINE),
+ ImageSpan(context, R.drawable.ic_legend_blocked, DynamicDrawableSpan.ALIGN_BASELINE),
span.length - 1,
span.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
diff --git a/app/src/main/java/foundation/e/advancedprivacy/widget/WidgetUI.kt b/app/src/main/java/foundation/e/advancedprivacy/widget/WidgetUI.kt
index 5d5a16392f4698cb3c6ae3e0bbb79ccb841b625c..3d58999b28e42b828617651318b70981c20fe377 100644
--- a/app/src/main/java/foundation/e/advancedprivacy/widget/WidgetUI.kt
+++ b/app/src/main/java/foundation/e/advancedprivacy/widget/WidgetUI.kt
@@ -34,7 +34,6 @@ import foundation.e.advancedprivacy.common.extensions.dpToPxF
import foundation.e.advancedprivacy.domain.entities.FeatureState
import foundation.e.advancedprivacy.domain.entities.QuickPrivacyState
import foundation.e.advancedprivacy.domain.entities.TrackerMode
-import foundation.e.advancedprivacy.features.dashboard.DashboardFragmentArgs
import foundation.e.advancedprivacy.main.MainActivity
import foundation.e.advancedprivacy.widget.WidgetCommandReceiver.Companion.ACTION_TOGGLE_IPSCRAMBLING
import foundation.e.advancedprivacy.widget.WidgetCommandReceiver.Companion.ACTION_TOGGLE_LOCATION
@@ -211,12 +210,6 @@ fun render(context: Context, state: State, appWidgetManager: AppWidgetManager) {
// leaked (the bar above)
val topPadding = graphHeightPx - (blocked + leaked) * ratio
setViewPadding(leakedBarIds[index], 0, topPadding.toInt(), 0, 0)
-
- val highlightPIntent = MainActivity.deepLinkBuilder(context)
- .setDestination(R.id.dashboardFragment)
- .setArguments(DashboardFragmentArgs(highlightLeaks = index).toBundle())
- .createPendingIntent()
- setOnClickPendingIntent(containerBarIds[index], highlightPIntent)
}
setTextViewText(
diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/drawable/ic_apps_24.xml
similarity index 61%
rename from app/src/main/res/values-night/colors.xml
rename to app/src/main/res/drawable/ic_apps_24.xml
index 6c1d53c04cc060611b8f3dd6c752551c2ac596cb..7e20536761b0deea124bdb8eca70f136022c074e 100644
--- a/app/src/main/res/values-night/colors.xml
+++ b/app/src/main/res/drawable/ic_apps_24.xml
@@ -1,6 +1,6 @@
-
-
- #169659
-
+
+
+
diff --git a/app/src/main/res/drawable/ic_apps_permissions.xml b/app/src/main/res/drawable/ic_apps_permissions.xml
deleted file mode 100644
index 5e7a5706deb50e0829b858aa96192bda088aacbf..0000000000000000000000000000000000000000
--- a/app/src/main/res/drawable/ic_apps_permissions.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/drawable/bg_rounded.xml b/app/src/main/res/drawable/ic_block_24.xml
similarity index 52%
rename from app/src/main/res/drawable/bg_rounded.xml
rename to app/src/main/res/drawable/ic_block_24.xml
index 0677165648091642d59dd3a41eaf79485dcdd79b..77537b3e58a525f2a18033c3bce930cc3b0c382e 100644
--- a/app/src/main/res/drawable/bg_rounded.xml
+++ b/app/src/main/res/drawable/ic_block_24.xml
@@ -1,5 +1,6 @@
-
-
-
-
-
-
-
-
+
+
+
diff --git a/app/src/main/res/drawable/ic_facebook.xml b/app/src/main/res/drawable/ic_facebook.xml
deleted file mode 100644
index 49597b3734ad309d20b868899fe230e713d40edf..0000000000000000000000000000000000000000
--- a/app/src/main/res/drawable/ic_facebook.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_internet_activity.xml b/app/src/main/res/drawable/ic_internet_activity.xml
deleted file mode 100644
index 83695ad6a61a2e4976da61575b946951ea2befa2..0000000000000000000000000000000000000000
--- a/app/src/main/res/drawable/ic_internet_activity.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_my_location.xml b/app/src/main/res/drawable/ic_my_location.xml
deleted file mode 100644
index 5d70d16ecb151fe4cc60d7da020923ed80a95744..0000000000000000000000000000000000000000
--- a/app/src/main/res/drawable/ic_my_location.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
diff --git a/app/src/main/res/drawable/ic_tracked.xml b/app/src/main/res/drawable/ic_tracked.xml
deleted file mode 100644
index 6cef537227a9365498de88a9e00e61ff3c9821a6..0000000000000000000000000000000000000000
--- a/app/src/main/res/drawable/ic_tracked.xml
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/chart_tooltip.xml b/app/src/main/res/layout/chart_tooltip.xml
index a88bc6fe2e92e32e23682ea20c38cf96e71dd871..6fabd50dd84e2fea7862bdec7d33500e06105866 100644
--- a/app/src/main/res/layout/chart_tooltip.xml
+++ b/app/src/main/res/layout/chart_tooltip.xml
@@ -1,4 +1,5 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/dashboard_item_submenu_button.xml b/app/src/main/res/layout/dashboard_item_submenu_button.xml
index ef03ea329365754848e3ec878e59b45bdda838bb..a4454c2dd52664803f44b86269e5fbc140b56a4b 100644
--- a/app/src/main/res/layout/dashboard_item_submenu_button.xml
+++ b/app/src/main/res/layout/dashboard_item_submenu_button.xml
@@ -1,4 +1,5 @@
-
-
-
-
-
-
-
-
+
+
-
+ android:layout_width="0dp"
-
+ app:layout_constraintLeft_toRightOf="@+id/switch_feature"
+ app:layout_constraintRight_toLeftOf="@+id/chevron"
-
+ app:layout_constraintTop_toTopOf="parent"
+
+ app:layout_constraintBottom_toTopOf="@+id/state_label"
+ app:layout_constraintVertical_chainStyle="packed"
+
+ android:maxLines="1"
+
+ android:layout_marginStart="8dp"
+ android:layout_marginBottom="8dp"
+
+ android:textColor="@color/primary_text"
+ android:textSize="14sp"
+ android:lineHeight="20dp"
+ android:textFontWeight="400"
+ android:textAllCaps="true"
+ tools:text="App trackers"
+ />
-
+
+
+
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_dashboard.xml b/app/src/main/res/layout/fragment_dashboard.xml
index b1fdaa02cdf65ac498ff82910649a1cacc0764cc..fd685c92705b2da3b518e92e7a89b05f1369a500 100644
--- a/app/src/main/res/layout/fragment_dashboard.xml
+++ b/app/src/main/res/layout/fragment_dashboard.xml
@@ -18,371 +18,120 @@
android:gravity="center_horizontal"
android:layout_height="match_parent"
android:layout_width="match_parent"
+ android:paddingHorizontal="16dp"
android:orientation="vertical"
>
-
-
+ android:layout_height="52dp"
+ android:layout_marginTop="16dp"
+ >
+
-
-
-
+
-
+
-
-
-
-
+ android:textSize="14sp"
+ android:lineHeight="24sp"
+ android:textFontWeight="500"
+ android:textColor="@color/primary_text"
+ android:layout_marginBottom="16dp"
+ android:text="@string/dashboard_settings_title"
+ />
-
-
-
-
- android:visibility="visible"/>
-
-
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:maxLines="1"
-
-
-
+ android:textColor="@color/primary_text"
+ android:textSize="14sp"
+ android:lineHeight="20dp"
+ android:textFontWeight="400"
+ android:textAllCaps="true"
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/app/src/main/res/layout/highlight_data_number.xml b/app/src/main/res/layout/highlight_data_number.xml
index 793816546ebce8a285776de34346bd064ab49979..4bb45636969591b236a621622b2051e42c28c138 100644
--- a/app/src/main/res/layout/highlight_data_number.xml
+++ b/app/src/main/res/layout/highlight_data_number.xml
@@ -32,7 +32,10 @@
android:textFontWeight="400"
android:textColor="@color/primary_text"
android:textAllCaps="true"
+ android:drawablePadding="8dp"
tools:text="Detected in"
+ tools:drawableStart="@drawable/ic_shield_alert"
+
/>
-
@color/e_switch_track_on
@color/e_palette_9
+ @color/e_switch_track_on
#32F8432E
#263238
#FFFFFFFF
- #28C97C
+
+
+
+
#AADCFE
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 3df052cb5864de8639022b9a69d50cae7c85648e..5177868b47d3b32974e2fe96d7122506d10b7721 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -36,34 +36,51 @@
@string/app_name
- Your online privacy is protected
- Your online privacy is unprotected
- Custom privacy settings applied
- Trackers
+
+ Last 30 days
+ View Statistics
+ Blocked
+ Leaks
+ From
+ Apps
+ Your Privacy Settings
+ App trackers
Vulnerable
Denied
Custom
- Location
+ Geolocation
Exposed
- Fake
- Real IP address
+ Protected
+ Real IP address
Exposed
Hidden
+ App permission request
+
+ Your online privacy is protected
+ Your online privacy is unprotected
+ Custom privacy settings applied
+ Trackers
+
+ Location
+
+ Real IP address
+
Personal data leakage:
Today
%s trackers have profiled you in the last 24 hours
View
Manage apps\' trackers
%1$d detected trackers, %2$d allowed trackers
- Manage apps\' permissions
+
Manage your permissions
- Manage my location
Real geolocation
Specific fake geolocation
Random fake geolocation
Manage my Internet address
Real IP address exposed
Real IP address hidden
+
+
Manage my Internet address
Your Internet address or IP address is the identifier assigned to your phone when connected to the Internet.\n\nManage my Internet address enables you to use a fake IP address instead of your real IP address. This way, your Internet activity cannot be linked to your real IP address and to your device.
diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/KoinModule.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/KoinModule.kt
index 34b4e7a863f6ae85a931acaa15efc06a1b1b5d5b..6a5bcef69a4dcf18d187a06ecd8cdcb41f7a502c 100644
--- a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/KoinModule.kt
+++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/KoinModule.kt
@@ -37,18 +37,10 @@ val trackersModule = module {
singleOf(::TrackersRepository)
single {
- StatsDatabase(
- context = androidContext(),
- trackersRepository = get()
- )
+ StatsDatabase(context = androidContext())
}
- single {
- StatisticsUseCase(
- database = get(),
- appListsRepository = get()
- )
- }
+ singleOf(::StatisticsUseCase)
single {
WhitelistRepository(
diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt
index 238de4e663ac780c15b5077073d2aac15d925d07..da6ee868454253aba683ca4494ed8035e7e7c9a8 100644
--- a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt
+++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/data/StatsDatabase.kt
@@ -31,7 +31,6 @@ import foundation.e.advancedprivacy.trackers.data.StatsDatabase.AppTrackerEntry.
import foundation.e.advancedprivacy.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_TIMESTAMP
import foundation.e.advancedprivacy.trackers.data.StatsDatabase.AppTrackerEntry.COLUMN_NAME_TRACKER
import foundation.e.advancedprivacy.trackers.data.StatsDatabase.AppTrackerEntry.TABLE_NAME
-import foundation.e.advancedprivacy.trackers.domain.entities.Tracker
import java.time.Instant
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter
@@ -45,8 +44,7 @@ import kotlinx.coroutines.withContext
import timber.log.Timber
class StatsDatabase(
- context: Context,
- private val trackersRepository: TrackersRepository
+ context: Context
) :
SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
@@ -247,75 +245,6 @@ class StatsDatabase(
}
}
- fun getContactedTrackersCount(): Int {
- synchronized(lock) {
- val db = readableDatabase
- var query = "SELECT DISTINCT $COLUMN_NAME_TRACKER FROM $TABLE_NAME"
-
- val cursor = db.rawQuery(query, arrayOf())
- var count = 0
- while (cursor.moveToNext()) {
- trackersRepository.getTracker(cursor.getString(COLUMN_NAME_TRACKER))?.let {
- count++
- }
- }
- cursor.close()
- db.close()
- return count
- }
- }
-
- fun getCalls(appId: String, periodCount: Int, periodUnit: TemporalUnit): Pair {
- synchronized(lock) {
- val minTimestamp = getPeriodStartTs(periodCount, periodUnit)
- val db = readableDatabase
- val selection = "$COLUMN_NAME_APPID = ? AND " +
- "$COLUMN_NAME_TIMESTAMP >= ?"
- val selectionArg = arrayOf("" + appId, "" + minTimestamp)
- val projection =
- "SUM($COLUMN_NAME_NUMBER_CONTACTED) $PROJECTION_NAME_CONTACTED_SUM," +
- "SUM($COLUMN_NAME_NUMBER_BLOCKED) $PROJECTION_NAME_BLOCKED_SUM"
- val cursor = db.rawQuery(
- "SELECT $projection FROM $TABLE_NAME WHERE $selection",
- selectionArg
- )
- var calls: Pair = 0 to 0
- if (cursor.moveToNext()) {
- val contacted = cursor.getInt(PROJECTION_NAME_CONTACTED_SUM)
- val blocked = cursor.getInt(PROJECTION_NAME_BLOCKED_SUM)
- calls = blocked to contacted - blocked
- }
- cursor.close()
- db.close()
- return calls
- }
- }
-
- fun getMostLeakedAppId(periodCount: Int, periodUnit: TemporalUnit): String {
- synchronized(lock) {
- val minTimestamp = getPeriodStartTs(periodCount, periodUnit)
- val db = readableDatabase
- val selection = "$COLUMN_NAME_TIMESTAMP >= ?"
- val selectionArg = arrayOf("" + minTimestamp)
- val projection = "$COLUMN_NAME_APPID, " +
- "SUM($COLUMN_NAME_NUMBER_CONTACTED - $COLUMN_NAME_NUMBER_BLOCKED) $PROJECTION_NAME_LEAKED_SUM"
- val cursor = db.rawQuery(
- "SELECT $projection FROM $TABLE_NAME" +
- " WHERE $selection" +
- " GROUP BY $COLUMN_NAME_APPID" +
- " ORDER BY $PROJECTION_NAME_LEAKED_SUM DESC LIMIT 1",
- selectionArg
- )
- var appId = ""
- if (cursor.moveToNext()) {
- appId = cursor.getString(COLUMN_NAME_APPID)
- }
- cursor.close()
- db.close()
- return appId
- }
- }
-
fun getDistinctTrackerAndApp(periodStart: Instant): List> {
synchronized(lock) {
val db = readableDatabase
@@ -332,7 +261,6 @@ class StatsDatabase(
null,
null,
null
-
)
val res = mutableListOf>()
@@ -424,6 +352,49 @@ class StatsDatabase(
}
}
+ suspend fun getBlockedLeaksCount(periodCount: Int, periodUnit: TemporalUnit): Int = withContext(Dispatchers.IO) {
+ synchronized(lock) {
+ val minTimestamp = getPeriodStartTs(periodCount, periodUnit)
+ val db = readableDatabase
+ val selection = "$COLUMN_NAME_TIMESTAMP >= ?"
+ val selectionArg = arrayOf("" + minTimestamp)
+ val projection = "SUM($COLUMN_NAME_NUMBER_BLOCKED) $PROJECTION_NAME_BLOCKED_SUM"
+ val cursor = db.rawQuery(
+ "SELECT $projection FROM $TABLE_NAME WHERE $selection",
+ selectionArg
+ )
+
+ var count = 0
+ if (cursor.moveToNext()) {
+ count = cursor.getInt(0)
+ }
+ cursor.close()
+ db.close()
+ count
+ }
+ }
+
+ suspend fun getAppsWithBLockedLeaksCount(periodCount: Int, periodUnit: TemporalUnit): Int = withContext(Dispatchers.IO) {
+ synchronized(lock) {
+ val minTimestamp = getPeriodStartTs(periodCount, periodUnit)
+ val db = readableDatabase
+ val selection = "$COLUMN_NAME_TIMESTAMP >= ? AND $COLUMN_NAME_NUMBER_BLOCKED > 0"
+ val selectionArg = arrayOf("" + minTimestamp)
+ val cursor = db.rawQuery(
+ "SELECT COUNT (DISTINCT $COLUMN_NAME_APPID) FROM $TABLE_NAME WHERE $selection",
+ selectionArg
+ )
+
+ var count = 0
+ if (cursor.moveToNext()) {
+ count = cursor.getInt(0)
+ }
+ cursor.close()
+ db.close()
+ count
+ }
+ }
+
suspend fun logAccess(trackerId: String?, appId: String, blocked: Boolean) = withContext(
Dispatchers.IO
) {
@@ -528,43 +499,6 @@ class StatsDatabase(
}
}
- suspend fun getTrackers(appIds: List?): List = withContext(Dispatchers.IO) {
- synchronized(lock) {
- val columns = arrayOf(COLUMN_NAME_TRACKER, COLUMN_NAME_APPID)
- var selection: String? = null
-
- var selectionArg: Array? = null
- appIds?.let { appIds ->
- selection = "$COLUMN_NAME_APPID IN (${appIds.joinToString(", ") { "'$it'" }})"
- selectionArg = arrayOf()
- }
-
- val db = readableDatabase
- val cursor = db.query(
- true,
- TABLE_NAME,
- columns,
- selection,
- selectionArg,
- null,
- null,
- null,
- null
- )
- val trackers: MutableList = ArrayList()
- while (cursor.moveToNext()) {
- val trackerId = cursor.getString(COLUMN_NAME_TRACKER)
- val tracker = trackersRepository.getTracker(trackerId)
- if (tracker != null) {
- trackers.add(tracker)
- }
- }
- cursor.close()
- db.close()
- trackers
- }
- }
-
class StatEntry {
var appId = ""
var sum_contacted = 0
diff --git a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/StatisticsUseCase.kt b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/StatisticsUseCase.kt
index d629b801380b5cf429895b3552a17bb12d6837ee..a36a6bf1859e07ac130c5fe76b52c048ed0834d2 100644
--- a/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/StatisticsUseCase.kt
+++ b/trackers/src/main/java/foundation/e/advancedprivacy/trackers/domain/usecases/StatisticsUseCase.kt
@@ -18,15 +18,11 @@
package foundation.e.advancedprivacy.trackers.domain.usecases
-import foundation.e.advancedprivacy.data.repositories.AppListsRepository
-import foundation.e.advancedprivacy.domain.entities.ApplicationDescription
import foundation.e.advancedprivacy.trackers.data.StatsDatabase
-import foundation.e.advancedprivacy.trackers.domain.entities.Tracker
import java.time.temporal.TemporalUnit
class StatisticsUseCase(
- private val database: StatsDatabase,
- private val appListsRepository: AppListsRepository
+ private val database: StatsDatabase
) {
fun getTrackersCallsOnPeriod(periodsCount: Int, periodUnit: TemporalUnit): List> {
return database.getTrackersCallsOnPeriod(periodsCount, periodUnit)
@@ -35,26 +31,4 @@ class StatisticsUseCase(
fun getActiveTrackersByPeriod(periodsCount: Int, periodUnit: TemporalUnit): Int {
return database.getActiveTrackersByPeriod(periodsCount, periodUnit)
}
-
- fun getContactedTrackersCount(): Int {
- return database.getContactedTrackersCount()
- }
-
- suspend fun getTrackers(apps: List?): List {
- return database.getTrackers(apps?.map { it.apId })
- }
-
- fun getCalls(app: ApplicationDescription, periodCount: Int, periodUnit: TemporalUnit): Pair {
- return database.getCalls(app.apId, periodCount, periodUnit)
- }
-
- fun getMostLeakedApp(periodCount: Int, periodUnit: TemporalUnit): ApplicationDescription? {
- return appListsRepository.getApp(database.getMostLeakedAppId(periodCount, periodUnit))
- }
-
- private fun Map.mapByAppIdToApp(): Map {
- return entries.mapNotNull { (apId, value) ->
- appListsRepository.getApp(apId)?.let { it to value }
- }.toMap()
- }
}