Refactor permissions requests to use ActivityResultContract (#4391)

The app currently uses a custom callback system for requesting
permissions, located in `BaseActivity`.
This code doesn't work when the activity gets re-created before the
permission is granted: the callback will be lost with the Activity and
no action will be performed after the permission is granted.

To avoid these issues while still simplifying the code, Google
recommends to use the ActivityResultContract APIs to request
permissions, which allow to register persistent callbacks. This pull
request removes the legacy API and replaces the calls with
`registerForActivityResult(ActivityResultContracts.RequestPermission())`.

- `ActivityResultContracts.RequestPermission` will check if the
permission is already granted and call the callback synchronously when
possible. So this check doesn't need to be performed anymore.
- In `SearchStatusesFragment` and `SFragment`, the download action to
launch after granting the permission requires an argument (a list of
media URLs). This argument is temporarily stored in a Fragment field and
saved and restored as part of the instance state, so the action can
properly resume after an Activity re-creation.
This commit is contained in:
Christophe Beyls 2024-05-03 21:42:35 +02:00 committed by GitHub
commit ad1afdd241
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 133 additions and 150 deletions

View file

@ -21,17 +21,19 @@ import android.content.ClipboardManager
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.util.Log
import android.view.MenuItem
import android.view.View
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.PopupMenu
import androidx.core.app.ActivityOptionsCompat
import androidx.core.content.getSystemService
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import at.connyduck.calladapter.networkresult.fold
@ -93,6 +95,22 @@ abstract class SFragment : Fragment(), Injectable {
@Inject
lateinit var instanceInfoRepository: InstanceInfoRepository
private var pendingMediaDownloads: List<String>? = null
private val downloadAllMediaPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted ->
if (isGranted) {
pendingMediaDownloads?.let { downloadAllMedia(it) }
} else {
Toast.makeText(
context,
R.string.error_media_download_permission,
Toast.LENGTH_SHORT
).show()
}
pendingMediaDownloads = null
}
override fun startActivity(intent: Intent) {
requireActivity().startActivityWithSlideInAnimation(intent)
}
@ -106,6 +124,18 @@ abstract class SFragment : Fragment(), Injectable {
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
pendingMediaDownloads = savedInstanceState?.getStringArrayList(PENDING_MEDIA_DOWNLOADS_STATE_KEY)
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
pendingMediaDownloads?.let {
outState.putStringArrayList(PENDING_MEDIA_DOWNLOADS_STATE_KEY, ArrayList(it))
}
}
override fun onResume() {
super.onResume()
@ -522,13 +552,11 @@ abstract class SFragment : Fragment(), Injectable {
}
}
private fun downloadAllMedia(status: Status) {
private fun downloadAllMedia(mediaUrls: List<String>) {
Toast.makeText(context, R.string.downloading_media, Toast.LENGTH_SHORT).show()
val downloadManager = requireActivity().getSystemService(
Context.DOWNLOAD_SERVICE
) as DownloadManager
val downloadManager: DownloadManager = requireContext().getSystemService()!!
for ((_, url) in status.attachments) {
for (url in mediaUrls) {
val uri = Uri.parse(url)
downloadManager.enqueue(
DownloadManager.Request(uri).apply {
@ -542,26 +570,22 @@ abstract class SFragment : Fragment(), Injectable {
}
private fun requestDownloadAllMedia(status: Status) {
if (status.attachments.isEmpty()) {
return
}
val mediaUrls = status.attachments.map { it.url }
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
val permissions = arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE)
(activity as BaseActivity).requestPermissions(permissions) { _, grantResults ->
if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
downloadAllMedia(status)
} else {
Toast.makeText(
context,
R.string.error_media_download_permission,
Toast.LENGTH_SHORT
).show()
}
}
pendingMediaDownloads = mediaUrls
downloadAllMediaPermissionLauncher.launch(Manifest.permission.WRITE_EXTERNAL_STORAGE)
} else {
downloadAllMedia(status)
downloadAllMedia(mediaUrls)
}
}
companion object {
private const val TAG = "SFragment"
private const val PENDING_MEDIA_DOWNLOADS_STATE_KEY = "pending_media_downloads"
private fun accountIsInMentions(
account: AccountEntity?,
mentions: List<Status.Mention>