Skip to content
Snippets Groups Projects
Commit c5c27b90 authored by Mugurell's avatar Mugurell Committed by Richard Pospesel
Browse files

Bug 1783561 - Allow a custom View for 3rd party downloads

This will allow clients easily implement their own UI

Backport of https://github.com/mozilla-mobile/firefox-android/commit/c53dd65718579006e174803acf683ffd44075049
parent e135dc89
Branches
Tags
1 merge request!93Bug 41679: Backport Android-specific security fixes from Firefox 111 to ESR 102.9-based Tor Browser (android-components portion)
......@@ -56,13 +56,25 @@ value class Filename(val value: String)
value class ContentSize(val value: Long)
/**
* Action for when the positive button of a download dialog was tapped.
* The list of all applications that can perform a download, including this application.
*/
@JvmInline
value class ThirdPartyDownloaderApps(val value: List<DownloaderApp>)
/**
* Callback for when the user picked a certain application with which to download the current file.
*/
@JvmInline
value class ThirdPartyDownloaderAppChosenCallback(val value: (DownloaderApp) -> Unit)
/**
* Callback for when the positive button of a download dialog was tapped.
*/
@JvmInline
value class PositiveActionCallback(val value: () -> Unit)
/**
* Action for when the negative button of a download dialog was tapped.
* Callback for when the negative button of a download dialog was tapped.
*/
@JvmInline
value class NegativeActionCallback(val value: () -> Unit)
......@@ -86,7 +98,10 @@ value class NegativeActionCallback(val value: () -> Unit)
* @property promptsStyling styling properties for the dialog.
* @property shouldForwardToThirdParties Indicates if downloads should be forward to third party apps,
* if there are multiple apps a chooser dialog will shown.
* @property customDownloadDialog An optional delegate for showing a download dialog.
* @property customFirstPartyDownloadDialog An optional delegate for showing a dialog for a download
* that will be processed by the current application.
* @property customThirdPartyDownloadDialog An optional delegate for showing a dialog for a download
* that can be processed by multiple installed applications including the current one.
*/
@Suppress("LongParameterList", "LargeClass")
class DownloadsFeature(
......@@ -101,7 +116,10 @@ class DownloadsFeature(
private val fragmentManager: FragmentManager? = null,
private val promptsStyling: PromptsStyling? = null,
private val shouldForwardToThirdParties: () -> Boolean = { false },
private val customDownloadDialog: ((Filename, ContentSize, PositiveActionCallback, NegativeActionCallback) -> Unit)? = null,
private val customFirstPartyDownloadDialog:
((Filename, ContentSize, PositiveActionCallback, NegativeActionCallback) -> Unit)? = null,
private val customThirdPartyDownloadDialog:
((ThirdPartyDownloaderApps, ThirdPartyDownloaderAppChosenCallback, NegativeActionCallback) -> Unit)? = null,
) : LifecycleAwareFeature, PermissionsFeature {
var onDownloadStopped: onDownloadStopped
......@@ -187,13 +205,25 @@ class DownloadsFeature(
val shouldShowAppDownloaderDialog = shouldForwardToThirdParties() && apps.size > 1
return if (shouldShowAppDownloaderDialog) {
showAppDownloaderDialog(tab, download, apps)
when (customThirdPartyDownloadDialog) {
null -> showAppDownloaderDialog(tab, download, apps)
else -> customThirdPartyDownloadDialog.invoke(
ThirdPartyDownloaderApps(apps),
ThirdPartyDownloaderAppChosenCallback {
onDownloaderAppSelected(it, tab, download)
},
NegativeActionCallback {
useCases.cancelDownloadRequest.invoke(tab.id, download.id)
},
)
}
false
} else {
if (applicationContext.isPermissionGranted(downloadManager.permissions.asIterable())) {
when {
customDownloadDialog != null && !download.skipConfirmation -> {
customDownloadDialog.invoke(
customFirstPartyDownloadDialog != null && !download.skipConfirmation -> {
customFirstPartyDownloadDialog.invoke(
Filename(download.realFilenameOrGuessed),
ContentSize(download.contentLength ?: 0),
PositiveActionCallback {
......@@ -309,6 +339,20 @@ class DownloadsFeature(
) {
appChooserDialog.setApps(apps)
appChooserDialog.onAppSelected = { app ->
onDownloaderAppSelected(app, tab, download)
}
appChooserDialog.onDismiss = {
useCases.cancelDownloadRequest.invoke(tab.id, download.id)
}
if (!isAlreadyAppDownloaderDialog() && fragmentManager != null && !fragmentManager.isDestroyed) {
appChooserDialog.showNow(fragmentManager, DownloadAppChooserDialog.FRAGMENT_TAG)
}
}
@VisibleForTesting
internal fun onDownloaderAppSelected(app: DownloaderApp, tab: SessionState, download: DownloadState) {
if (app.packageName == applicationContext.packageName) {
if (applicationContext.isPermissionGranted(downloadManager.permissions.asIterable())) {
startDownload(download)
......@@ -322,7 +366,7 @@ class DownloadsFeature(
} catch (error: ActivityNotFoundException) {
val errorMessage = applicationContext.getString(
R.string.mozac_feature_downloads_unable_to_open_third_party_app,
app.name
app.name,
)
Toast.makeText(applicationContext, errorMessage, Toast.LENGTH_SHORT).show()
}
......@@ -330,15 +374,6 @@ class DownloadsFeature(
}
}
appChooserDialog.onDismiss = {
useCases.cancelDownloadRequest.invoke(tab.id, download.id)
}
if (!isAlreadyAppDownloaderDialog() && fragmentManager != null && !fragmentManager.isDestroyed) {
appChooserDialog.showNow(fragmentManager, DownloadAppChooserDialog.FRAGMENT_TAG)
}
}
private fun getAppDownloaderDialog() = findPreviousAppDownloaderDialogFragment()
?: DownloadAppChooserDialog.newInstance(
promptsStyling?.gravity,
......
......@@ -16,10 +16,10 @@ import mozilla.components.feature.downloads.R
/**
* An adapter for displaying the applications that can perform downloads.
*/
internal class DownloaderAppAdapter(
class DownloaderAppAdapter(
context: Context,
private val apps: List<DownloaderApp>,
val onAppSelected: ((DownloaderApp) -> Unit)
val onAppSelected: ((DownloaderApp) -> Unit),
) : RecyclerView.Adapter<DownloaderAppViewHolder>() {
private val inflater = LayoutInflater.from(context)
......@@ -49,11 +49,14 @@ internal class DownloaderAppAdapter(
/**
* View holder for a [DownloaderApp] item.
*/
internal class DownloaderAppViewHolder(
class DownloaderAppViewHolder(
itemView: View,
val nameLabel: TextView,
val iconImage: ImageView
val iconImage: ImageView,
) : RecyclerView.ViewHolder(itemView) {
/**
* Show a certain downloader application in the current View.
*/
fun bind(app: DownloaderApp, onAppSelected: ((DownloaderApp) -> Unit)) {
itemView.app = app
itemView.setOnClickListener {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment