Skip to content

Commit

Permalink
Close issue mozilla-mobile#9823 Make users aware that download was no…
Browse files Browse the repository at this point in the history
…t performed because of a denied permission
  • Loading branch information
Amejia481 committed Mar 3, 2021
1 parent 9be7ff5 commit c998be6
Show file tree
Hide file tree
Showing 18 changed files with 176 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import mozilla.components.feature.downloads.manager.onDownloadStopped
import mozilla.components.feature.downloads.ui.DownloaderApp
import mozilla.components.feature.downloads.ui.DownloadAppChooserDialog
import mozilla.components.lib.state.ext.flowScoped
import mozilla.components.support.base.dialog.DeniedPermissionDialogFragment
import mozilla.components.support.base.feature.LifecycleAwareFeature
import mozilla.components.support.base.feature.OnNeedToRequestPermissions
import mozilla.components.support.base.feature.PermissionsFeature
Expand Down Expand Up @@ -213,6 +214,7 @@ class DownloadsFeature(
} else {
closeDownloadResponse(tab.id)
useCases.consumeDownload(tab.id, download.id)
showPermissionDeniedDialog()
}
}
}
Expand Down Expand Up @@ -394,6 +396,16 @@ class DownloadsFeature(
val positiveButtonTextColor: Int? = null,
val positiveButtonRadius: Float? = null
)

@VisibleForTesting
internal fun showPermissionDeniedDialog() {
fragmentManager?.let {
val dialog = DeniedPermissionDialogFragment.newInstance(
R.string.mozac_feature_downloads_write_external_storage_permissions_needed_message
)
dialog.showNow(fragmentManager, DeniedPermissionDialogFragment.FRAGMENT_TAG)
}
}
}

@VisibleForTesting
Expand Down
2 changes: 2 additions & 0 deletions components/feature/downloads/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,6 @@
<string name="mozac_feature_downloads_third_party_app_chooser_dialog_title">Complete action using</string>
<!-- Message that appears when trying to download with an external app fails. %1$s will be replaced with the name the external app. -->-->
<string name="mozac_feature_downloads_unable_to_open_third_party_app">Unable to open %1$s</string>
<!-- Text for the info dialog when write to storage permissions have been denied but user tries to download a file. -->
<string name="mozac_feature_downloads_write_external_storage_permissions_needed_message">Storage access needed to download files. Go to Android settings, tap permissions, and tap allow.</string>
</resources>
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,7 @@ class DownloadsFeatureTest {

verify(downloadManager, never()).download(any(), anyString())
verify(feature).closeDownloadResponse("test-tab")
verify(feature).showPermissionDeniedDialog()
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import mozilla.components.feature.prompts.PromptFeature
import mozilla.components.feature.prompts.R
import mozilla.components.feature.prompts.R.id
import mozilla.components.support.test.mock
import mozilla.ext.appCompatContext
import mozilla.components.support.test.ext.appCompatContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.feature.prompts.R.id
import mozilla.components.support.test.mock
import mozilla.ext.appCompatContext
import mozilla.components.support.test.ext.appCompatContext
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ import mozilla.components.feature.prompts.dialog.ChoiceDialogFragment.Companion.
import mozilla.components.feature.prompts.dialog.ChoiceDialogFragment.Companion.MULTIPLE_CHOICE_DIALOG_TYPE
import mozilla.components.feature.prompts.dialog.ChoiceDialogFragment.Companion.SINGLE_CHOICE_DIALOG_TYPE
import mozilla.components.feature.prompts.dialog.ChoiceDialogFragment.Companion.newInstance
import mozilla.components.support.test.ext.appCompatContext
import mozilla.components.support.test.robolectric.testContext
import mozilla.ext.appCompatContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNotNull
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.feature.prompts.R
import mozilla.components.support.test.mock
import mozilla.components.support.test.ext.appCompatContext
import mozilla.components.support.test.robolectric.testContext
import mozilla.ext.appCompatContext
import org.junit.Assert.assertEquals
import org.junit.Before
import org.junit.Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import android.content.DialogInterface
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.ext.appCompatContext
import mozilla.components.support.test.ext.appCompatContext
import org.junit.Assert
import org.junit.Assert.assertEquals
import org.junit.Before
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.feature.prompts.R
import mozilla.components.feature.prompts.R.id
import mozilla.components.support.test.mock
import mozilla.ext.appCompatContext
import mozilla.components.support.test.ext.appCompatContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@ import junit.framework.TestCase
import mozilla.components.concept.storage.Login
import mozilla.components.feature.prompts.R
import mozilla.components.support.test.any
import mozilla.components.support.test.ext.appCompatContext
import mozilla.components.support.test.mock
import mozilla.ext.appCompatContext
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.`when`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import androidx.core.view.isVisible
import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.feature.prompts.R.id
import mozilla.components.support.test.mock
import mozilla.ext.appCompatContext
import mozilla.components.support.test.ext.appCompatContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ import mozilla.components.feature.prompts.ext.year
import mozilla.components.support.ktx.kotlin.toDate
import mozilla.components.support.test.any
import mozilla.components.support.test.eq
import mozilla.components.support.test.ext.appCompatContext
import mozilla.components.support.test.mock
import mozilla.components.support.test.robolectric.testContext
import mozilla.ext.appCompatContext
import org.junit.Assert.assertEquals
import org.junit.Assert.assertTrue
import org.junit.Before
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.concept.storage.Login
import mozilla.components.feature.prompts.R
import mozilla.components.support.test.ext.appCompatContext
import mozilla.components.support.test.mock
import mozilla.components.support.test.robolectric.testContext
import mozilla.ext.appCompatContext
import org.junit.Assert.assertFalse
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.support.base.dialog

import android.app.Dialog
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.provider.Settings
import androidx.annotation.StringRes
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import mozilla.components.support.base.R

internal const val KEY_MESSAGE = "KEY_MESSAGE"

/**
* An dialog to display when Android permission ise denied and
* you want give users a way activate it on the app settings.
* The dialog will have two buttons one "Go to settings" and another for "Dismissing".
*/
class DeniedPermissionDialogFragment : DialogFragment() {
internal val message: Int by lazy { safeArguments.getInt(KEY_MESSAGE) }
val safeArguments get() = requireNotNull(arguments)

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val builder = AlertDialog.Builder(requireContext())
.setMessage(message)
.setCancelable(true)
.setNegativeButton(R.string.mozac_support_base_permissions_needed_negative_button) { _, _ ->
dismiss()
}
.setPositiveButton(R.string.mozac_support_base_permissions_needed_positive_button) { _, _ ->
openSettingsPage()
}
return builder.create()
}

@VisibleForTesting
internal fun openSettingsPage() {
dismiss()
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
val uri = Uri.fromParts("package", requireContext().packageName, null)
intent.data = uri
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
requireContext().startActivity(intent)
}

companion object {
/**
* A builder method for creating a [DeniedPermissionDialogFragment]
* @param message the message of the dialog.
**/
fun newInstance(
@StringRes message: Int
): DeniedPermissionDialogFragment {

val fragment = DeniedPermissionDialogFragment()
val arguments = fragment.arguments ?: Bundle()

with(arguments) {
putInt(KEY_MESSAGE, message)
}

fragment.arguments = arguments
return fragment
}

const val FRAGMENT_TAG = "DENIED_DOWNLOAD_PERMISSION_PROMPT_DIALOG"
}
}
10 changes: 10 additions & 0 deletions components/support/base/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<resources>
<!-- Text for the positive action button, that will take the user to the settings page -->
<string name="mozac_support_base_permissions_needed_positive_button">Go to settings</string>
<!-- Text for the negative action button to dismiss the dialog. -->
<string name="mozac_support_base_permissions_needed_negative_button">Dismiss</string>
</resources>
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.components.support.base.dialog

import android.content.DialogInterface.BUTTON_POSITIVE
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.test.ext.junit.runners.AndroidJUnit4
import mozilla.components.support.base.R
import mozilla.components.support.test.ext.appCompatContext
import mozilla.components.support.test.robolectric.testContext
import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.doNothing
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.spy
import org.mockito.Mockito.verify

@RunWith(AndroidJUnit4::class)
class DeniedPermissionDialogFragmentTest {

@Test
fun `WHEN showing the dialog THEN it has the provided message`() {
val messageId = R.string.mozac_support_base_permissions_needed_negative_button
val fragment = spy(
DeniedPermissionDialogFragment.newInstance(messageId)
)

doReturn(appCompatContext).`when`(fragment).requireContext()

val dialog = fragment.onCreateDialog(null)

dialog.show()

val messageTextView = dialog.findViewById<TextView>(android.R.id.message)

assertEquals(fragment.message, messageId)
assertEquals(messageTextView.text.toString(), testContext.getString(messageId))
}

@Test
fun `WHEN clicking the positive button THEN the settings page will show`() {
val messageId = R.string.mozac_support_base_permissions_needed_negative_button

val fragment = spy(
DeniedPermissionDialogFragment.newInstance(messageId)
)

doNothing().`when`(fragment).dismiss()
doReturn(appCompatContext).`when`(fragment).requireContext()

val dialog = fragment.onCreateDialog(null)
dialog.show()

val positiveButton = (dialog as AlertDialog).getButton(BUTTON_POSITIVE)
positiveButton.performClick()

verify(fragment).openSettingsPage()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */

package mozilla.ext
package mozilla.components.support.test.ext

import android.content.Context
import androidx.annotation.VisibleForTesting
import androidx.appcompat.R
import androidx.appcompat.view.ContextThemeWrapper
import mozilla.components.support.test.robolectric.testContext
Expand All @@ -14,5 +15,5 @@ import mozilla.components.support.test.robolectric.testContext
*
* Useful for views that uses theme attributes, for example.
*/
internal val appCompatContext: Context
@VisibleForTesting val appCompatContext: Context
get() = ContextThemeWrapper(testContext, R.style.Theme_AppCompat)
1 change: 1 addition & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ permalink: /changelog/
* **feature-downloads**:
* 🚒 Bug fixed [issue #9757](/~https://github.com/mozilla-mobile/android-components/issues/9757) - Remove downloads notification when private tabs are closed.
* 🚒 Bug fixed [issue #9789](/~https://github.com/mozilla-mobile/android-components/issues/9789) - Canceled first PDF download prevents following attempts from downloading.
* 🚒 Bug fixed [issue #9823](/~https://github.com/mozilla-mobile/android-components/issues/9823) - Downloads prompts do not show again when a user denies system permission twice.

* **concept-engine**,**browser-engine-gecko**, **browser-engine-gecko-beta**, **browser-engine-gecko-nightly**, **browser-engine-system**
* ⚠️ **This is a breaking change**: `EngineSession`.`enableTrackingProtection()` and `EngineSession`.`disableTrackingProtection()` have been removed, please use `EngineSession`.`updateTrackingProtection()` instead , for more details see [issue #9787](/~https://github.com/mozilla-mobile/android-components/issues/9787).
Expand Down

0 comments on commit c998be6

Please sign in to comment.