diff options
Diffstat (limited to 'app')
13 files changed, 230 insertions, 139 deletions
diff --git a/app/build.gradle b/app/build.gradle index 8812137..b771285 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -110,7 +110,7 @@ dependencies { //googleImplementation project(":privacymodulesgoogle") // include the e specific version of the modules, just for the e flavor - implementation 'foundation.e:privacymodule.trackerfilter:0.2.0' + implementation 'foundation.e:privacymodule.trackerfilter:0.3.0' implementation 'foundation.e:privacymodule.api:0.5.0' e29Implementation 'foundation.e:privacymodule.e-29:0.4.2' e30Implementation 'foundation.e:privacymodule.e-30:0.4.2' diff --git a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt index c8d7fb2..76a9539 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/DependencyContainer.kt @@ -20,6 +20,7 @@ package foundation.e.privacycentralapp import android.app.Application import android.content.Context import android.os.Process +import foundation.e.privacycentralapp.data.repositories.AppListsRepository import foundation.e.privacycentralapp.data.repositories.LocalStateRepository import foundation.e.privacycentralapp.data.repositories.TrackersRepository import foundation.e.privacycentralapp.domain.usecases.AppListUseCase @@ -73,22 +74,26 @@ class DependencyContainer constructor(val app: Application) { // Repositories private val localStateRepository by lazy { LocalStateRepository(context) } private val trackersRepository by lazy { TrackersRepository(context) } + private val appListsRepository by lazy { AppListsRepository(permissionsModule, context, GlobalScope) } // Usecases private val getQuickPrivacyStateUseCase by lazy { GetQuickPrivacyStateUseCase(localStateRepository) } private val ipScramblingStateUseCase by lazy { - IpScramblingStateUseCase(ipScramblerModule, permissionsModule, appDesc, localStateRepository, GlobalScope) + IpScramblingStateUseCase( + ipScramblerModule, permissionsModule, appDesc, localStateRepository, + appListsRepository, GlobalScope + ) } - private val appListUseCase = AppListUseCase(permissionsModule, blockTrackersPrivacyModule, GlobalScope) + private val appListUseCase = AppListUseCase(appListsRepository) private val trackersStatisticsUseCase by lazy { - TrackersStatisticsUseCase(trackTrackersPrivacyModule, context.resources) + TrackersStatisticsUseCase(trackTrackersPrivacyModule, appListsRepository, context.resources) } private val trackersStateUseCase by lazy { - TrackersStateUseCase(blockTrackersPrivacyModule, trackTrackersPrivacyModule, permissionsModule, localStateRepository, trackersRepository, GlobalScope) + TrackersStateUseCase(blockTrackersPrivacyModule, trackTrackersPrivacyModule, permissionsModule, localStateRepository, trackersRepository, appListsRepository, GlobalScope) } private val fakeLocationStateUseCase by lazy { diff --git a/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt new file mode 100644 index 0000000..4718923 --- /dev/null +++ b/app/src/main/java/foundation/e/privacycentralapp/data/repositories/AppListsRepository.kt @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2022 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 <https://www.gnu.org/licenses/>. + */ + +package foundation.e.privacycentralapp.data.repositories + +import android.Manifest +import android.content.Context +import android.content.Intent +import android.content.pm.ApplicationInfo +import foundation.e.privacymodules.permissions.PermissionsPrivacyModule +import foundation.e.privacymodules.permissions.data.ApplicationDescription +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.launch + +class AppListsRepository( + private val permissionsModule: PermissionsPrivacyModule, + private val context: Context, + private val coroutineScope: CoroutineScope +) { + val dummySystemApp = permissionsModule.getApplicationDescription("com.android.settings") + + fun getVisibleApps(): Flow<List<ApplicationDescription>> { + coroutineScope.launch { + val (visible, hidden) = splitVisibleToHidden(getAppsUsingInternet()) + appDescriptions.emit( + visible.map { it.toApplicationDescription(withIcon = true) } + to hidden.map { it.toApplicationDescription() } + ) + } + return appDescriptions.map { it.first.sortedBy { app -> app.label.toString().lowercase() } } + } + fun getHiddenSystemApps(): List<ApplicationDescription> { + return appDescriptions.value.second + } + + private val pm get() = context.packageManager + + private val appDescriptions = MutableStateFlow( + emptyList<ApplicationDescription>() to emptyList<ApplicationDescription>() + ) + + private fun getAppsUsingInternet(): List<ApplicationInfo> { + return pm.getInstalledApplications(0) + .filter { + permissionsModule.getPermissions(it.packageName) + .contains(Manifest.permission.INTERNET) + } + } + + private fun isNotHiddenSystemApp(app: ApplicationInfo, launcherApps: List<String>): Boolean { + if (app.hasFlag(ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) { + return true + } else if (!app.hasFlag(ApplicationInfo.FLAG_SYSTEM)) { + return true + } else if (launcherApps.contains(app.packageName)) { + return true + } + return false + } + + private fun ApplicationInfo.hasFlag(flag: Int) = (flags and flag) == 1 + + private fun splitVisibleToHidden(apps: List<ApplicationInfo>): Pair<List<ApplicationInfo>, List<ApplicationInfo>> { + val launcherPackageNames = pm.queryIntentActivities( + Intent(Intent.ACTION_MAIN, null).apply { addCategory(Intent.CATEGORY_LAUNCHER) }, + 0 + ).mapNotNull { it.activityInfo?.packageName } + return apps.fold( + mutableListOf<ApplicationInfo>() to mutableListOf<ApplicationInfo>() + ) { + acc, app -> + if (isNotHiddenSystemApp(app, launcherPackageNames)) { + acc.first.add(app) + } else { + acc.second.add(app) + } + acc + } + } + + private fun ApplicationInfo.toApplicationDescription(withIcon: Boolean = true) = ApplicationDescription( + packageName = packageName, + uid = uid, + label = pm.getApplicationLabel(this), + icon = if (withIcon) pm.getApplicationIcon(packageName) else null + ) +} diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt index acb8a36..4821349 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/AppListUseCase.kt @@ -17,112 +17,15 @@ package foundation.e.privacycentralapp.domain.usecases -import android.Manifest -import foundation.e.privacymodules.permissions.PermissionsPrivacyModule +import foundation.e.privacycentralapp.data.repositories.AppListsRepository import foundation.e.privacymodules.permissions.data.ApplicationDescription -import foundation.e.privacymodules.trackers.api.BlockTrackersPrivacyModule -import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.launch class AppListUseCase( - private val permissionsModule: PermissionsPrivacyModule, - private val blockTrackersPrivacyModule: BlockTrackersPrivacyModule, - private val corouteineScope: CoroutineScope + private val appListsRepository: AppListsRepository ) { - private val _appsUsingInternet = MutableStateFlow<List<ApplicationDescription>>(emptyList()) - private val _appsBlockableTrackers = MutableStateFlow<List<ApplicationDescription>>(emptyList()) - init { - corouteineScope.launch { - _appsUsingInternet.value = getAppsUsingInternetList() - } - } - - fun getBlockableApps(): Flow<List<ApplicationDescription>> { - corouteineScope.launch { - _appsBlockableTrackers.value = getBlockableAppsList() - } - return _appsBlockableTrackers - } - - private fun getBlockableAppsList(): List<ApplicationDescription> { - return blockTrackersPrivacyModule.getBlockableApps() - .filter { - permissionsModule.getPermissions(it.packageName) - .contains(Manifest.permission.INTERNET) - }.map { - it.icon = permissionsModule.getApplicationIcon(it.packageName) - it - }.sortedWith(object : Comparator<ApplicationDescription> { - override fun compare( - p0: ApplicationDescription?, - p1: ApplicationDescription? - ): Int { - return if (p0?.icon != null && p1?.icon != null) { - p0.label.toString().compareTo(p1.label.toString()) - } else if (p0?.icon == null) { - 1 - } else { - -1 - } - } - }) - } - - private fun getInstalledAppsUsingInternetList(): List<ApplicationDescription> { - return permissionsModule.getInstalledApplications() - .filter { - permissionsModule.getPermissions(it.packageName) - .contains(Manifest.permission.INTERNET) - }.map { - it.icon = permissionsModule.getApplicationIcon(it.packageName) - it - }.sortedWith(object : Comparator<ApplicationDescription> { - override fun compare( - p0: ApplicationDescription?, - p1: ApplicationDescription? - ): Int { - return if (p0?.icon != null && p1?.icon != null) { - p0.label.toString().compareTo(p1.label.toString()) - } else if (p0?.icon == null) { - 1 - } else { - -1 - } - } - }) - } - fun getAppsUsingInternet(): Flow<List<ApplicationDescription>> { - corouteineScope.launch { - _appsUsingInternet.value = getAppsUsingInternetList() - } - return _appsUsingInternet - } - - private fun getAppsUsingInternetList(): List<ApplicationDescription> { - return permissionsModule.getAllApplications() - .filter { - permissionsModule.getPermissions(it.packageName) - .contains(Manifest.permission.INTERNET) - }.map { - it.icon = permissionsModule.getApplicationIcon(it.packageName) - it - }.sortedWith(object : Comparator<ApplicationDescription> { - override fun compare( - p0: ApplicationDescription?, - p1: ApplicationDescription? - ): Int { - return if (p0?.icon != null && p1?.icon != null) { - p0.label.toString().compareTo(p1.label.toString()) - } else if (p0?.icon == null) { - 1 - } else { - -1 - } - } - }) + return appListsRepository.getVisibleApps() } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt index 237e5b2..6cc8e4a 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/IpScramblingStateUseCase.kt @@ -17,6 +17,7 @@ package foundation.e.privacycentralapp.domain.usecases +import foundation.e.privacycentralapp.data.repositories.AppListsRepository import foundation.e.privacycentralapp.data.repositories.LocalStateRepository import foundation.e.privacycentralapp.domain.entities.InternetPrivacyMode import foundation.e.privacymodules.ipscramblermodule.IIpScramblerModule @@ -36,10 +37,10 @@ class IpScramblingStateUseCase( private val permissionsPrivacyModule: IPermissionsPrivacyModule, private val appDesc: ApplicationDescription, private val localStateRepository: LocalStateRepository, + private val appListsRepository: AppListsRepository, coroutineScope: CoroutineScope ) { - // private val internetPrivacyModeMutableFlow = MutableStateFlow(InternetPrivacyMode.REAL_IP) val internetPrivacyMode: StateFlow<InternetPrivacyMode> = callbackFlow { val listener = object : IIpScramblerModule.Listener { override fun onStatusChanged(newStatus: IIpScramblerModule.Status) { @@ -79,16 +80,41 @@ class IpScramblingStateUseCase( applySettings(true, hideIp) } - val bypassTorApps: Set<String> get() = ipScramblerModule.appList + private fun getHiddenPackageNames(): List<String> { + return appListsRepository.getHiddenSystemApps().map { it.packageName } + } + + val bypassTorApps: Set<String> get() { + var whitelist = ipScramblerModule.appList + + if (getHiddenPackageNames().any { it in whitelist }) { + val mutable = whitelist.toMutableSet() + mutable.removeAll(getHiddenPackageNames()) + mutable.add(appListsRepository.dummySystemApp.packageName) + whitelist = mutable + } + + return whitelist + } fun toggleBypassTor(packageName: String) { - val currentList = bypassTorApps.toMutableSet() - if (currentList.contains(packageName)) { - currentList.remove(packageName) + val visibleList = bypassTorApps.toMutableSet() + val rawList = ipScramblerModule.appList.toMutableSet() + + if (visibleList.contains(packageName)) { + if (packageName == appListsRepository.dummySystemApp.packageName) { + rawList.removeAll(getHiddenPackageNames()) + } else { + rawList.remove(packageName) + } } else { - currentList.add(packageName) + if (packageName == appListsRepository.dummySystemApp.packageName) { + rawList.addAll(getHiddenPackageNames()) + } else { + rawList.add(packageName) + } } - ipScramblerModule.appList = currentList + ipScramblerModule.appList = rawList } private fun applySettings(isQuickPrivacyEnabled: Boolean, isIpScramblingEnabled: Boolean) { diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt index e8759cb..16a1a82 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStateUseCase.kt @@ -17,6 +17,7 @@ package foundation.e.privacycentralapp.domain.usecases +import foundation.e.privacycentralapp.data.repositories.AppListsRepository import foundation.e.privacycentralapp.data.repositories.LocalStateRepository import foundation.e.privacycentralapp.data.repositories.TrackersRepository import foundation.e.privacymodules.permissions.PermissionsPrivacyModule @@ -36,6 +37,7 @@ class TrackersStateUseCase( private val permissionsPrivacyModule: PermissionsPrivacyModule, private val localStateRepository: LocalStateRepository, private val trackersRepository: TrackersRepository, + private val appListsRepository: AppListsRepository, coroutineScope: CoroutineScope ) { @@ -64,25 +66,44 @@ class TrackersStateUseCase( blockTrackersPrivacyModule.isWhiteListEmpty() } - fun getApplicationPermission(packageName: String): ApplicationDescription { + fun getApplicationDescription(packageName: String): ApplicationDescription { return permissionsPrivacyModule.getApplicationDescription(packageName) } fun isWhitelisted(appUid: Int): Boolean { - return blockTrackersPrivacyModule.isWhitelisted(appUid) + return if (appUid == appListsRepository.dummySystemApp.uid) { + appListsRepository.getHiddenSystemApps().any { + blockTrackersPrivacyModule.isWhitelisted(it.uid) + } + } else blockTrackersPrivacyModule.isWhitelisted(appUid) } fun getTrackersWhitelistIds(appUid: Int): List<String> { - return blockTrackersPrivacyModule.getWhiteList(appUid).map { it.id } + return if (appUid == appListsRepository.dummySystemApp.uid) { + appListsRepository.getHiddenSystemApps().fold(mutableSetOf<String>()) { acc, app -> + acc.addAll(blockTrackersPrivacyModule.getWhiteList(app.uid).map { it.id }) + acc + }.toList() + } else blockTrackersPrivacyModule.getWhiteList(appUid).map { it.id } } fun toggleAppWhitelist(appUid: Int, isWhitelisted: Boolean) { - blockTrackersPrivacyModule.setWhiteListed(appUid, isWhitelisted) + if (appUid == appListsRepository.dummySystemApp.uid) { + appListsRepository.getHiddenSystemApps().forEach { + blockTrackersPrivacyModule.setWhiteListed(it.uid, isWhitelisted) + } + } else blockTrackersPrivacyModule.setWhiteListed(appUid, isWhitelisted) + updateAllTrackersBlockedState() } fun blockTracker(appUid: Int, tracker: Tracker, isBlocked: Boolean) { - blockTrackersPrivacyModule.setWhiteListed(tracker, appUid, !isBlocked) + if (appUid == appListsRepository.dummySystemApp.uid) { + appListsRepository.getHiddenSystemApps().forEach { + blockTrackersPrivacyModule.setWhiteListed(tracker, it.uid, !isBlocked) + } + } else blockTrackersPrivacyModule.setWhiteListed(tracker, appUid, !isBlocked) + updateAllTrackersBlockedState() } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt index ad5c86c..9a8b12a 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/domain/usecases/TrackersStatisticsUseCase.kt @@ -19,6 +19,7 @@ package foundation.e.privacycentralapp.domain.usecases import android.content.res.Resources import foundation.e.privacycentralapp.R +import foundation.e.privacycentralapp.data.repositories.AppListsRepository import foundation.e.privacycentralapp.domain.entities.TrackersPeriodicStatistics import foundation.e.privacymodules.trackers.ITrackTrackersPrivacyModule import foundation.e.privacymodules.trackers.Tracker @@ -31,6 +32,7 @@ import java.time.temporal.ChronoUnit class TrackersStatisticsUseCase( private val trackTrackersPrivacyModule: ITrackTrackersPrivacyModule, + private val appListsRepository: AppListsRepository, private val resources: Resources ) { @@ -114,6 +116,12 @@ class TrackersStatisticsUseCase( } fun getTrackers(appUid: Int): List<Tracker> { - return trackTrackersPrivacyModule.getTrackersForApp(appUid) + val trackers = if (appUid == appListsRepository.dummySystemApp.uid) { + appListsRepository.getHiddenSystemApps().map { + trackTrackersPrivacyModule.getTrackersForApp(it.uid) + }.flatten().distinctBy { it.id } + } else trackTrackersPrivacyModule.getTrackersForApp(appUid) + + return trackers.sortedBy { it.label.lowercase() } } } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt index 00e3fb7..e2eb58d 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/TrackersFeature.kt @@ -119,7 +119,7 @@ class TrackersFeature( ) } }, - appListUseCase.getBlockableApps().map { apps -> + appListUseCase.getAppsUsingInternet().map { apps -> Effect.AvailableAppsListEffect(apps) }, trackersStatisticsUseCase.listenUpdates().map { diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFeature.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFeature.kt index ff0c9db..16cd4a0 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFeature.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFeature.kt @@ -118,7 +118,7 @@ class AppTrackersFeature( when (action) { is Action.InitAction -> { val appDesc = - trackersStateUseCase.getApplicationPermission(action.packageName) + trackersStateUseCase.getApplicationDescription(action.packageName) merge<Effect>( flow { @@ -154,9 +154,7 @@ class AppTrackersFeature( emit( Effect.AppTrackersBlockingActivatedEffect( - !trackersStateUseCase.isWhitelisted( - appUid - ) + !trackersStateUseCase.isWhitelisted(appUid) ) ) } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt index 1f339ee..440edf7 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/AppTrackersFragment.kt @@ -97,7 +97,6 @@ class AppTrackersFragment : super.onViewCreated(view, savedInstanceState) binding = ApptrackersFragmentBinding.bind(view) - // TODO: crash sqlite ? binding.blockAllToggle.setOnClickListener { viewModel.submitAction(Action.BlockAllToggleAction(binding.blockAllToggle.isChecked)) } diff --git a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt index 0ab3987..580a60c 100644 --- a/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt +++ b/app/src/main/java/foundation/e/privacycentralapp/features/trackers/apptrackers/ToggleTrackersAdapter.kt @@ -22,6 +22,7 @@ import android.view.View import android.view.ViewGroup import android.widget.Switch import android.widget.TextView +import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import foundation.e.privacycentralapp.R import foundation.e.privacymodules.trackers.Tracker @@ -38,11 +39,13 @@ class ToggleTrackersAdapter( val title: TextView = view.findViewById(R.id.title) val toggle: Switch = view.findViewById(R.id.toggle) + val toggleOverlay: View = view.findViewById(R.id.toggle_clicker) fun bind(item: Pair<Tracker, Boolean>, isEnabled: Boolean) { title.text = item.first.label toggle.isChecked = item.second toggle.isEnabled = isEnabled + toggleOverlay.isVisible = !isEnabled } } @@ -58,9 +61,13 @@ class ToggleTrackersAdapter( val view = LayoutInflater.from(parent.context) .inflate(itemsLayout, parent, false) val holder = ViewHolder(view) - holder.itemView.setOnClickListener { + holder.toggle.setOnClickListener { listener(dataSet[holder.adapterPosition].first, holder.toggle.isChecked) } + holder.toggleOverlay.setOnClickListener { + listener(dataSet[holder.adapterPosition].first, false) + } + return holder } diff --git a/app/src/main/res/layout/apptrackers_item_tracker_toggle.xml b/app/src/main/res/layout/apptrackers_item_tracker_toggle.xml index baab3d9..20e0bdc 100644 --- a/app/src/main/res/layout/apptrackers_item_tracker_toggle.xml +++ b/app/src/main/res/layout/apptrackers_item_tracker_toggle.xml @@ -1,7 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> -<androidx.appcompat.widget.LinearLayoutCompat - xmlns:android="http://schemas.android.com/apk/res/android" +<androidx.appcompat.widget.LinearLayoutCompat xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/container" android:layout_height="52dp" android:layout_width="match_parent" @@ -21,10 +21,28 @@ tools:text="Body sensor" /> - <Switch - android:id="@+id/toggle" + <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="wrap_content" - android:layout_height="24dp" - android:checked="true" - /> + android:layout_height="wrap_content"> + <Switch + android:id="@+id/toggle" + android:layout_width="wrap_content" + android:layout_height="24dp" + android:checked="true" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + /> + <View + android:id="@+id/toggle_clicker" + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + android:clickable="true" + /> + </androidx.constraintlayout.widget.ConstraintLayout> </androidx.appcompat.widget.LinearLayoutCompat> diff --git a/app/src/main/res/layout/item_app_toggle.xml b/app/src/main/res/layout/item_app_toggle.xml index d0f565f..8bee0e2 100644 --- a/app/src/main/res/layout/item_app_toggle.xml +++ b/app/src/main/res/layout/item_app_toggle.xml @@ -1,6 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" + xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/item_permission_apps" android:layout_height="wrap_content" android:layout_width="match_parent" @@ -34,11 +35,12 @@ tools:text="Body sensor" /> - <Switch - android:id="@+id/toggle" - android:layout_alignParentEnd="true" - android:layout_centerVertical="true" - android:layout_height="wrap_content" - android:layout_width="wrap_content" - /> + <Switch + android:id="@+id/toggle" + android:layout_alignParentEnd="true" + android:layout_centerVertical="true" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + /> + </RelativeLayout>
\ No newline at end of file |