Skip to content

Commit

Permalink
feat: automatically detect Files app restrictions
Browse files Browse the repository at this point in the history
  • Loading branch information
aliernfrog committed Apr 15, 2024
1 parent 63a3dfd commit 5dee938
Show file tree
Hide file tree
Showing 16 changed files with 140 additions and 199 deletions.
4 changes: 3 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ plugins {
val composeMaterialVersion = "1.6.2"
val composeMaterial3Version = "1.2.0"
val composeCompilerVersion = "1.5.10"
val lifecycleVersion = "2.7.0"
val shizukuVersion = "13.1.5"

android {
Expand Down Expand Up @@ -87,7 +88,8 @@ dependencies {
implementation("androidx.compose.material:material-icons-extended:$composeMaterialVersion")
implementation("androidx.compose.material3:material3:$composeMaterial3Version")
implementation("androidx.compose.material3:material3-window-size-class:$composeMaterial3Version")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.7.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:$lifecycleVersion")
implementation("androidx.lifecycle:lifecycle-runtime-compose:$lifecycleVersion")
implementation("androidx.activity:activity-compose:1.8.2")
implementation("androidx.navigation:navigation-compose:2.7.7")
implementation("io.insert-koin:koin-androidx-compose:3.5.3")
Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
</application>

<queries>
<package android:name="com.google.android.documentsui"/>
<package android:name="moe.shizuku.privileged.api"/>
</queries>
</manifest>
1 change: 1 addition & 0 deletions app/src/main/java/com/aliernfrog/pftool/Constant.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const val TAG = "PFToolLogs"
const val experimentalSettingsRequiredClicks = 10
const val githubRepoURL = "https://github.com/aliernfrog/pf-tool"
const val crowdinURL = "https://crowdin.com/project/pf-tool"
const val documentsUIPackageName = "com.google.android.documentsui"

val externalStorageRoot = Environment.getExternalStorageDirectory().toString()+"/"
val supportsPerAppLanguagePreferences = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ data class PermissionData(
@StringRes val title: Int,
val recommendedPath: String?,
@StringRes val recommendedPathDescription: Int?,
@StringRes val createFolderHint: Int? = null,
@StringRes val recommendedPathWarning: Int? = null,
@StringRes val useUnrecommendedAnywayDescription: Int? = null,
val forceRecommendedPath: Boolean = true,
val getUri: () -> String,
Expand Down
55 changes: 0 additions & 55 deletions app/src/main/java/com/aliernfrog/pftool/enum/SAFWorkaroundLevel.kt

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import com.aliernfrog.pftool.R
import com.aliernfrog.pftool.data.PermissionData
import com.aliernfrog.pftool.externalStorageRoot
import com.aliernfrog.pftool.folderPickerSupportsInitialUri
import com.aliernfrog.pftool.hasAndroidDataRestrictions
import com.aliernfrog.pftool.ui.component.form.ButtonRow

@Composable
Expand Down Expand Up @@ -83,7 +82,6 @@ fun UnrecommendedFolderDialog(
permissionData: PermissionData,
chosenUri: Uri,
onDismissRequest: () -> Unit,
onFolderDoesNotExist: () -> Unit,
onUseUnrecommendedFolderRequest: () -> Unit,
onChooseFolderRequest: () -> Unit
) {
Expand Down Expand Up @@ -120,12 +118,6 @@ fun UnrecommendedFolderDialog(
)
}

if (hasAndroidDataRestrictions) ClickableText(
text = stringResource(R.string.permissions_recommendedFolder_doesNotExist)
) {
onFolderDoesNotExist()
}

if (chosenUri != Uri.EMPTY) ClickableText(
text = stringResource(
if (showAdvancedOptions) R.string.permissions_notRecommendedFolder_advanced_hide
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ fun MapsPermissionsScreen(
title = R.string.permissions_maps,
recommendedPath = ConfigKey.RECOMMENDED_MAPS_DIR,
recommendedPathDescription = R.string.permissions_maps_recommended,
createFolderHint = R.string.permissions_maps_openPFToCreate,
recommendedPathWarning = R.string.permissions_maps_openPFToCreate,
useUnrecommendedAnywayDescription = R.string.permissions_maps_useUnrecommendedAnyway,
getUri = { mapsViewModel.prefs.pfMapsDir },
onUriUpdate = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
package com.aliernfrog.pftool.ui.screen.permissions

import android.content.Intent
import android.net.Uri
import android.provider.Settings
import androidx.compose.animation.AnimatedContent
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.neverEqualPolicy
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect
import com.aliernfrog.pftool.R
import com.aliernfrog.pftool.data.PermissionData
import com.aliernfrog.pftool.enum.StorageAccessType
import com.aliernfrog.pftool.ui.component.AppScaffold
Expand Down Expand Up @@ -39,11 +48,15 @@ fun PermissionsScreen(
)
}

var permissionsGranted by remember { mutableStateOf(hasPermissions(), neverEqualPolicy()) }

LifecycleEventEffect(Lifecycle.Event.ON_RESUME) {
permissionsGranted = hasPermissions()
}

AnimatedContent(
StorageAccessType.entries[permissionsViewModel.prefs.storageAccessType]
) { method ->
var permissionsGranted by remember { mutableStateOf(hasPermissions()) }

AnimatedContent(permissionsGranted) { showContent ->
if (showContent) content()
else AppScaffold(
Expand Down Expand Up @@ -72,12 +85,30 @@ fun PermissionsScreen(
}
}

if (permissionsViewModel.showSAFWorkaroundDialog) permissionsViewModel.safWorkaroundLevel.let { level ->
CustomMessageDialog(
title = level.title?.let { stringResource(it) },
text = level.description?.let { stringResource(it) },
confirmButton = level.button,
onDismissRequest = { permissionsViewModel.showSAFWorkaroundDialog = false }
)
}
if (permissionsViewModel.showShizukuIntroDialog) CustomMessageDialog(
title = stringResource(R.string.permissions_setupShizuku),
text = stringResource(R.string.permissions_setupShizuku_description),
onDismissRequest = { permissionsViewModel.showShizukuIntroDialog = false }
)

if (permissionsViewModel.showFilesDowngradeDialog) CustomMessageDialog(
title = null,
text = stringResource(R.string.permissions_downgradeFilesApp_guide)
.replace("{CANT_UNINSTALL_TEXT}", stringResource(R.string.permissions_downgradeFilesApp_cant)),
onDismissRequest = { permissionsViewModel.showFilesDowngradeDialog = false },
confirmButton = {
Button(
onClick = {
permissionsViewModel.showFilesDowngradeDialog = false
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS).apply {
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
data = Uri.parse("package:com.google.android.documentsui")
context.startActivity(this)
}
}
) {
Text(stringResource(R.string.action_ok))
}
}
)
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
package com.aliernfrog.pftool.ui.screen.permissions

import android.net.Uri
import android.os.Environment
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.ListItem
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
Expand All @@ -32,7 +32,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import com.aliernfrog.pftool.R
import com.aliernfrog.pftool.data.PermissionData
import com.aliernfrog.pftool.enum.SAFWorkaroundLevel
import com.aliernfrog.pftool.enum.StorageAccessType
import com.aliernfrog.pftool.ui.component.CardWithActions
import com.aliernfrog.pftool.ui.component.form.DividerRow
import com.aliernfrog.pftool.ui.dialog.ChooseFolderIntroDialog
import com.aliernfrog.pftool.ui.dialog.UnrecommendedFolderDialog
Expand All @@ -41,10 +42,61 @@ import com.aliernfrog.pftool.util.extension.requiresAndroidData
import com.aliernfrog.pftool.util.extension.toPath
import com.aliernfrog.pftool.util.extension.takePersistablePermissions
import com.aliernfrog.pftool.util.staticutil.FileUtil
import com.aliernfrog.pftool.util.staticutil.GeneralUtil
import org.koin.androidx.compose.koinViewModel

@Composable
fun SAFPermissionsScreen(
vararg permissionsData: PermissionData,
onUpdateStateRequest: () -> Unit
) {
val context = LocalContext.current
val requiresAndroidData = permissionsData.any { it.requiresAndroidData }
val needsToDowngradeFiles = requiresAndroidData && GeneralUtil.filesAppRestrictsAndroidData(context)

AnimatedContent(needsToDowngradeFiles) {
if (it) DowngradeFiles()
else SAFPermissionsList(
*permissionsData, onUpdateStateRequest = onUpdateStateRequest
)
}
}

@Composable
private fun DowngradeFiles(
permissionsViewModel: PermissionsViewModel = koinViewModel()
) {
Column(
modifier = Modifier
.fillMaxSize()
.verticalScroll(rememberScrollState())
) {
CardWithActions(
modifier = Modifier.padding(8.dp),
title = stringResource(R.string.permissions_downgradeFilesApp),
buttons = {
TextButton(
onClick = {
permissionsViewModel.showShizukuIntroDialog = true
StorageAccessType.SHIZUKU.enable(permissionsViewModel.prefs)
}
) {
Text(stringResource(R.string.permissions_downgradeFilesApp_cant))
}
Button(
onClick = { permissionsViewModel.showFilesDowngradeDialog = true }
) {
Text(stringResource(R.string.permissions_downgradeFilesApp_uninstall))
}
}
) {
Text(stringResource(R.string.permissions_downgradeFilesApp_description))
}
}
}

@Composable
private fun SAFPermissionsList(
vararg permissionsData: PermissionData,
permissionsViewModel: PermissionsViewModel = koinViewModel(),
onUpdateStateRequest: () -> Unit
Expand Down Expand Up @@ -116,12 +168,14 @@ fun SAFPermissionsScreen(
Column(Modifier.fillMaxWidth()) {
permissionData.content()

val isAndroidData = permissionData.forceRecommendedPath && permissionData.recommendedPath
?.startsWith("${Environment.getExternalStorageDirectory()}/Android/data") == true
if (isAndroidData) Guide(
level = permissionsViewModel.safWorkaroundLevel,
permissionData = permissionData
)
permissionData.recommendedPathWarning?.let { warning ->
Card(Modifier.padding(vertical = 8.dp)) {
Text(
text = stringResource(warning),
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp)
)
}
}

Button(
onClick = ::onClick,
Expand All @@ -147,11 +201,6 @@ fun SAFPermissionsScreen(
permissionData = activePermissionData!!,
chosenUri = uri,
onDismissRequest = { unrecommendedPathWarningUri = null },
onFolderDoesNotExist = {
unrecommendedPathWarningUri = null
permissionsViewModel.pushSAFWorkaroundLevel()
permissionsViewModel.showSAFWorkaroundDialog = true
},
onUseUnrecommendedFolderRequest = {
takePersistableUriPermissions(uri)
unrecommendedPathWarningUri = null
Expand All @@ -162,43 +211,4 @@ fun SAFPermissionsScreen(
}
)
}
}

@Composable
private fun Guide(
level: SAFWorkaroundLevel,
permissionData: PermissionData
) {
val title = level.title
val description = if (level == SAFWorkaroundLevel.MAKE_SURE_FOLDER_EXISTS) permissionData.createFolderHint
else level.description

if (title != null || description != null) Card(
modifier = Modifier.padding(vertical = 4.dp)
) {
Column(
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
title?.let {
Text(
text = stringResource(it),
style = MaterialTheme.typography.titleMedium
)
}
description?.let {
Text(
text = stringResource(it)
)
}
level.button?.let { button ->
Row(
horizontalArrangement = Arrangement.aligned(Alignment.End),
modifier = Modifier.fillMaxWidth()
) {
button()
}
}
}
}
}
Loading

0 comments on commit 5dee938

Please sign in to comment.