android: changed permissions handling UI in status screen when show rationale is false
Android will ignore permission request and will not show the request dialog if the user's action implies "don't ask again." This leaves the app in a crippled state and the user confused. Google says "don't try to convince the user", so it returns false for `shouldShowRequestPermissionRationale`. To help the user proceed, we show the `Request permission` button only if `shouldShowRequestPermissionRationale == true` because there's a good chance the premission request dialog will not be ignored. If `shouldShowRequestPermissionRationale == false` we instead show the "rationale" message and a button to open the app info dialog where the user can explicitly grand the permission.
This commit is contained in:
parent
cb62aff43e
commit
51242be72b
android
@ -6,6 +6,9 @@ Notes and resources for MPD android maintainers.
|
||||
|
||||
### Version control
|
||||
|
||||
|
||||
git ignoring .idea directory completely until a good reason emerges not to
|
||||
|
||||
* [How to manage projects under Version Control Systems (jetbrains.com)](https://intellij-support.jetbrains.com/hc/en-us/articles/206544839-How-to-manage-projects-under-Version-Control-Systems)
|
||||
|
||||
* [gradle.xml should work like workspace.xml? (jetbrains.com)](https://youtrack.jetbrains.com/issue/IDEA-55923)
|
||||
@ -15,3 +18,30 @@ Notes and resources for MPD android maintainers.
|
||||
* [Include prebuilt native libraries (developer.android.com)](https://developer.android.com/studio/projects/gradle-external-native-builds#jniLibs)
|
||||
|
||||
|
||||
### Permissions
|
||||
|
||||
#### Files access
|
||||
|
||||
The required permission depends on android SDK version:
|
||||
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU)
|
||||
Manifest.permission.READ_MEDIA_AUDIO
|
||||
else
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
|
||||
#### Permission request
|
||||
|
||||
[Request runtime permissions](https://developer.android.com/training/permissions/requesting)
|
||||
|
||||
Since Android 6.0 (API level 23):
|
||||
|
||||
Android will ignore permission request and will not show the request dialog
|
||||
if the user's action implies "don't ask again."
|
||||
This leaves the app in a crippled state and the user confused.
|
||||
Google says "don't try to convince the user", so it returns false for `shouldShowRequestPermissionRationale`.
|
||||
|
||||
To help the user proceed, we show the `Request permission` button only if `shouldShowRequestPermissionRationale == true`
|
||||
because there's a good chance the permission request dialog will not be ignored.
|
||||
|
||||
If `shouldShowRequestPermissionRationale == false` we instead show the "rationale" message and a button to open
|
||||
the app info dialog where the user can explicitly grand the permission.
|
@ -1,6 +1,7 @@
|
||||
package org.musicpd.ui
|
||||
|
||||
import android.Manifest
|
||||
import android.os.Build
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
@ -24,54 +25,36 @@ import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.google.accompanist.permissions.ExperimentalPermissionsApi
|
||||
import com.google.accompanist.permissions.PermissionState
|
||||
import com.google.accompanist.permissions.isGranted
|
||||
import com.google.accompanist.permissions.rememberPermissionState
|
||||
import com.google.accompanist.permissions.shouldShowRationale
|
||||
import org.musicpd.R
|
||||
import org.musicpd.utils.openAppSettings
|
||||
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Composable
|
||||
fun StatusScreen(settingsViewModel: SettingsViewModel) {
|
||||
val storagePermissionState = rememberPermissionState(
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
)
|
||||
|
||||
if (storagePermissionState.status.shouldShowRationale) {
|
||||
Column(
|
||||
Modifier
|
||||
.padding(4.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(stringResource(id = R.string.external_files_permission_request))
|
||||
Button(onClick = { }) {
|
||||
Text("Request permission")
|
||||
}
|
||||
}
|
||||
val storagePermissionState = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
rememberPermissionState(
|
||||
Manifest.permission.READ_MEDIA_AUDIO
|
||||
)
|
||||
} else {
|
||||
Column(
|
||||
Modifier
|
||||
.padding(4.dp)
|
||||
.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
NetworkAddress()
|
||||
ServerStatus(settingsViewModel)
|
||||
if (!storagePermissionState.status.isGranted) {
|
||||
OutlinedButton(
|
||||
onClick = { storagePermissionState.launchPermissionRequest() }, Modifier
|
||||
.padding(4.dp)
|
||||
.fillMaxWidth()
|
||||
) {
|
||||
Text(
|
||||
"Request external storage permission",
|
||||
color = MaterialTheme.colorScheme.secondary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
rememberPermissionState(
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
)
|
||||
}
|
||||
|
||||
Column(
|
||||
Modifier
|
||||
.padding(4.dp)
|
||||
.fillMaxSize(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
NetworkAddress()
|
||||
ServerStatus(settingsViewModel)
|
||||
AudioMediaPermission(storagePermissionState)
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,4 +99,44 @@ fun ServerStatus(settingsViewModel: SettingsViewModel) {
|
||||
Text(text = statusUiState.statusMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalPermissionsApi::class)
|
||||
@Composable
|
||||
fun AudioMediaPermission(storagePermissionState: PermissionState) {
|
||||
val permissionStatus = storagePermissionState.status
|
||||
if (!permissionStatus.isGranted) {
|
||||
val context = LocalContext.current
|
||||
Column(
|
||||
Modifier
|
||||
.padding(4.dp)
|
||||
.fillMaxWidth(),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Text(
|
||||
stringResource(id = R.string.external_files_permission_request),
|
||||
Modifier.padding(16.dp)
|
||||
)
|
||||
if (storagePermissionState.status.shouldShowRationale) {
|
||||
Button(onClick = {
|
||||
storagePermissionState.launchPermissionRequest()
|
||||
}) {
|
||||
Text("Request permission")
|
||||
}
|
||||
} else {
|
||||
OutlinedButton(
|
||||
onClick = {
|
||||
openAppSettings(context, context.packageName)
|
||||
},
|
||||
Modifier.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
stringResource(id = R.string.title_open_app_info),
|
||||
color = MaterialTheme.colorScheme.secondary
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
30
android/app/src/main/java/org/musicpd/utils/IntentUtils.kt
Normal file
30
android/app/src/main/java/org/musicpd/utils/IntentUtils.kt
Normal file
@ -0,0 +1,30 @@
|
||||
package org.musicpd.utils
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
|
||||
private const val TAG = "IntentUtils"
|
||||
|
||||
fun openAppSettings(
|
||||
context: Context,
|
||||
packageName: String
|
||||
) {
|
||||
try {
|
||||
context.startActivity(Intent().apply {
|
||||
setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||
setData(Uri.parse("package:$packageName"))
|
||||
addCategory(Intent.CATEGORY_DEFAULT)
|
||||
addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY)
|
||||
addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS)
|
||||
})
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.e(
|
||||
TAG,
|
||||
"failed to open app settings for package: $packageName", e
|
||||
)
|
||||
}
|
||||
}
|
@ -10,4 +10,5 @@
|
||||
<string name="checkbox_wakelock">Prevent suspend when MPD is running (Wakelock)</string>
|
||||
<string name="checkbox_pause_on_headphones_disconnect">Pause MPD when headphones disconnect</string>
|
||||
<string name="external_files_permission_request">MPD requires access to external files to play local music. Please grant the permission.</string>
|
||||
<string name="title_open_app_info">Open app info</string>
|
||||
</resources>
|
||||
|
Loading…
x
Reference in New Issue
Block a user