Bug 1917919 - Add 'Learn more' button to the addon installation dialog. r=android-reviewers,amejiamarmol,zmckenney,gmalekpour,willdurand

Differential Revision: https://phabricator.services.mozilla.com/D221826
This commit is contained in:
Tara
2024-10-23 15:45:21 +00:00
parent 3351a0f780
commit 8909938c32
8 changed files with 119 additions and 2 deletions

View File

@@ -48,6 +48,8 @@ open class AddonDialogFragment : AppCompatDialogFragment() {
@ColorRes @ColorRes
val confirmButtonBackgroundColor: Int? = null, val confirmButtonBackgroundColor: Int? = null,
@ColorRes @ColorRes
val learnMoreLinkTextColor: Int? = null,
@ColorRes
val confirmButtonTextColor: Int? = null, val confirmButtonTextColor: Int? = null,
val confirmButtonRadius: Float? = null, val confirmButtonRadius: Float? = null,
) )

View File

@@ -8,6 +8,7 @@ import android.annotation.SuppressLint
import android.app.Dialog import android.app.Dialog
import android.content.DialogInterface import android.content.DialogInterface
import android.graphics.Color import android.graphics.Color
import android.graphics.Paint
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.GradientDrawable import android.graphics.drawable.GradientDrawable
import android.os.Bundle import android.os.Bundle
@@ -35,6 +36,7 @@ private const val KEY_DIALOG_WIDTH_MATCH_PARENT = "KEY_DIALOG_WIDTH_MATCH_PARENT
private const val KEY_POSITIVE_BUTTON_BACKGROUND_COLOR = "KEY_POSITIVE_BUTTON_BACKGROUND_COLOR" private const val KEY_POSITIVE_BUTTON_BACKGROUND_COLOR = "KEY_POSITIVE_BUTTON_BACKGROUND_COLOR"
private const val KEY_POSITIVE_BUTTON_TEXT_COLOR = "KEY_POSITIVE_BUTTON_TEXT_COLOR" private const val KEY_POSITIVE_BUTTON_TEXT_COLOR = "KEY_POSITIVE_BUTTON_TEXT_COLOR"
private const val KEY_POSITIVE_BUTTON_RADIUS = "KEY_POSITIVE_BUTTON_RADIUS" private const val KEY_POSITIVE_BUTTON_RADIUS = "KEY_POSITIVE_BUTTON_RADIUS"
private const val KEY_LEARN_MORE_LINK_TEXT_COLOR = "KEY_LEARN_MORE_LINK_TEXT_COLOR"
private const val KEY_FOR_OPTIONAL_PERMISSIONS = "KEY_FOR_OPTIONAL_PERMISSIONS" private const val KEY_FOR_OPTIONAL_PERMISSIONS = "KEY_FOR_OPTIONAL_PERMISSIONS"
internal const val KEY_PERMISSIONS = "KEY_PERMISSIONS" internal const val KEY_PERMISSIONS = "KEY_PERMISSIONS"
private const val DEFAULT_VALUE = Int.MAX_VALUE private const val DEFAULT_VALUE = Int.MAX_VALUE
@@ -55,6 +57,11 @@ class PermissionsDialogFragment : AddonDialogFragment() {
*/ */
var onNegativeButtonClicked: (() -> Unit)? = null var onNegativeButtonClicked: (() -> Unit)? = null
/**
* A lambda called when the learn more link is clicked.
*/
var onLearnMoreClicked: (() -> Unit)? = null
internal val addon get() = requireNotNull(safeArguments.getParcelableCompat(KEY_ADDON, Addon::class.java)) internal val addon get() = requireNotNull(safeArguments.getParcelableCompat(KEY_ADDON, Addon::class.java))
internal val positiveButtonRadius internal val positiveButtonRadius
@@ -85,6 +92,13 @@ class PermissionsDialogFragment : AddonDialogFragment() {
DEFAULT_VALUE, DEFAULT_VALUE,
) )
internal val learnMoreLinkTextColor
get() =
safeArguments.getInt(
KEY_LEARN_MORE_LINK_TEXT_COLOR,
DEFAULT_VALUE,
)
/** /**
* This flag is used to adjust the permissions prompt for optional permissions (instead of asking * This flag is used to adjust the permissions prompt for optional permissions (instead of asking
* users to grant the required permissions at install time, which is the default). * users to grant the required permissions at install time, which is the default).
@@ -160,6 +174,9 @@ class PermissionsDialogFragment : AddonDialogFragment() {
rootView.findViewById<TextView>(R.id.optional_or_required_text).text = rootView.findViewById<TextView>(R.id.optional_or_required_text).text =
buildOptionalOrRequiredText(listPermissions.isNotEmpty()) buildOptionalOrRequiredText(listPermissions.isNotEmpty())
val learnMoreLink = rootView.findViewById<TextView>(R.id.learn_more_link)
learnMoreLink.paintFlags = Paint.UNDERLINE_TEXT_FLAG
val permissionsRecyclerView = rootView.findViewById<RecyclerView>(R.id.permissions) val permissionsRecyclerView = rootView.findViewById<RecyclerView>(R.id.permissions)
val positiveButton = rootView.findViewById<Button>(R.id.allow_button) val positiveButton = rootView.findViewById<Button>(R.id.allow_button)
val negativeButton = rootView.findViewById<Button>(R.id.deny_button) val negativeButton = rootView.findViewById<Button>(R.id.deny_button)
@@ -211,6 +228,13 @@ class PermissionsDialogFragment : AddonDialogFragment() {
dismiss() dismiss()
} }
if (learnMoreLinkTextColor != DEFAULT_VALUE) {
val color = ContextCompat.getColor(requireContext(), learnMoreLinkTextColor)
learnMoreLink.setTextColor(color)
}
learnMoreLink.setOnClickListener {
onLearnMoreClicked?.invoke()
}
return rootView return rootView
} }
@@ -243,6 +267,7 @@ class PermissionsDialogFragment : AddonDialogFragment() {
* @param promptsStyling Styling properties for the dialog. * @param promptsStyling Styling properties for the dialog.
* @param onPositiveButtonClicked A lambda called when the allow button is clicked. * @param onPositiveButtonClicked A lambda called when the allow button is clicked.
* @param onNegativeButtonClicked A lambda called when the deny button is clicked. * @param onNegativeButtonClicked A lambda called when the deny button is clicked.
* @param onLearnMoreClicked A lambda called when the learn more button is clicked.
*/ */
fun newInstance( fun newInstance(
addon: Addon, addon: Addon,
@@ -254,6 +279,7 @@ class PermissionsDialogFragment : AddonDialogFragment() {
), ),
onPositiveButtonClicked: ((Addon, Boolean) -> Unit)? = null, onPositiveButtonClicked: ((Addon, Boolean) -> Unit)? = null,
onNegativeButtonClicked: (() -> Unit)? = null, onNegativeButtonClicked: (() -> Unit)? = null,
onLearnMoreClicked: (() -> Unit)? = null,
): PermissionsDialogFragment { ): PermissionsDialogFragment {
val fragment = PermissionsDialogFragment() val fragment = PermissionsDialogFragment()
val arguments = fragment.arguments ?: Bundle() val arguments = fragment.arguments ?: Bundle()
@@ -272,13 +298,16 @@ class PermissionsDialogFragment : AddonDialogFragment() {
promptsStyling?.confirmButtonBackgroundColor?.apply { promptsStyling?.confirmButtonBackgroundColor?.apply {
putInt(KEY_POSITIVE_BUTTON_BACKGROUND_COLOR, this) putInt(KEY_POSITIVE_BUTTON_BACKGROUND_COLOR, this)
} }
promptsStyling?.confirmButtonTextColor?.apply { promptsStyling?.confirmButtonTextColor?.apply {
putInt(KEY_POSITIVE_BUTTON_TEXT_COLOR, this) putInt(KEY_POSITIVE_BUTTON_TEXT_COLOR, this)
} }
promptsStyling?.learnMoreLinkTextColor?.apply {
putInt(KEY_LEARN_MORE_LINK_TEXT_COLOR, this)
}
} }
fragment.onPositiveButtonClicked = onPositiveButtonClicked fragment.onPositiveButtonClicked = onPositiveButtonClicked
fragment.onNegativeButtonClicked = onNegativeButtonClicked fragment.onNegativeButtonClicked = onNegativeButtonClicked
fragment.onLearnMoreClicked = onLearnMoreClicked
fragment.arguments = arguments fragment.arguments = arguments
return fragment return fragment
} }

View File

@@ -56,6 +56,15 @@
android:textColor="?android:attr/textColorPrimary" android:textColor="?android:attr/textColorPrimary"
tools:text="It requires your permission to:" /> tools:text="It requires your permission to:" />
<TextView
android:id="@+id/learn_more_link"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/allow_in_private_browsing"
android:layout_alignStart="@id/title"
android:layout_marginTop="30dp"
android:text="@string/mozac_feature_addons_permissions_dialog_learn_more" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/permissions" android:id="@+id/permissions"
android:layout_width="match_parent" android:layout_width="match_parent"

View File

@@ -133,6 +133,8 @@
<string name="mozac_feature_addons_permissions_dialog_allow">Allow</string> <string name="mozac_feature_addons_permissions_dialog_allow">Allow</string>
<!-- This is a button to deny the optional permissions requested by an add-on . --> <!-- This is a button to deny the optional permissions requested by an add-on . -->
<string name="mozac_feature_addons_permissions_dialog_deny">Deny</string> <string name="mozac_feature_addons_permissions_dialog_deny">Deny</string>
<!-- Text link to learn more about permission requests on the add-on permissions dialog . -->
<string name="mozac_feature_addons_permissions_dialog_learn_more">Learn more</string>
<!-- This is a button to cancel the add-on installation . --> <!-- This is a button to cancel the add-on installation . -->
<string name="mozac_feature_addons_permissions_dialog_cancel">Cancel</string> <string name="mozac_feature_addons_permissions_dialog_cancel">Cancel</string>
<!-- Accessibility content description to install add-on button. %1$s is the add-on name. --> <!-- Accessibility content description to install add-on button. %1$s is the add-on name. -->

View File

@@ -79,6 +79,7 @@ class PermissionsDialogFragmentTest {
val fragment = createPermissionsDialogFragment(addon, permissions = emptyList()) val fragment = createPermissionsDialogFragment(addon, permissions = emptyList())
var allowedWasExecuted = false var allowedWasExecuted = false
var denyWasExecuted = false var denyWasExecuted = false
var learnMoreWasExecuted = false
fragment.onPositiveButtonClicked = { _, _ -> fragment.onPositiveButtonClicked = { _, _ ->
allowedWasExecuted = true allowedWasExecuted = true
@@ -88,6 +89,10 @@ class PermissionsDialogFragmentTest {
denyWasExecuted = true denyWasExecuted = true
} }
fragment.onLearnMoreClicked = {
learnMoreWasExecuted = true
}
doReturn(testContext).`when`(fragment).requireContext() doReturn(testContext).`when`(fragment).requireContext()
val dialog = fragment.onCreateDialog(null) val dialog = fragment.onCreateDialog(null)
@@ -95,12 +100,15 @@ class PermissionsDialogFragmentTest {
val positiveButton = dialog.findViewById<Button>(R.id.allow_button) val positiveButton = dialog.findViewById<Button>(R.id.allow_button)
val negativeButton = dialog.findViewById<Button>(R.id.deny_button) val negativeButton = dialog.findViewById<Button>(R.id.deny_button)
val learnMoreLink = dialog.findViewById<TextView>(R.id.learn_more_link)
positiveButton.performClick() positiveButton.performClick()
negativeButton.performClick() negativeButton.performClick()
learnMoreLink.performClick()
assertTrue(allowedWasExecuted) assertTrue(allowedWasExecuted)
assertTrue(denyWasExecuted) assertTrue(denyWasExecuted)
assertTrue(learnMoreWasExecuted)
} }
@Test @Test

View File

@@ -60,6 +60,7 @@ import mozilla.components.concept.engine.EngineSession
import mozilla.components.concept.engine.EngineView import mozilla.components.concept.engine.EngineView
import mozilla.components.concept.storage.HistoryMetadataKey import mozilla.components.concept.storage.HistoryMetadataKey
import mozilla.components.feature.contextmenu.DefaultSelectionActionDelegate import mozilla.components.feature.contextmenu.DefaultSelectionActionDelegate
import mozilla.components.feature.customtabs.isCustomTabIntent
import mozilla.components.feature.media.ext.findActiveMediaTab import mozilla.components.feature.media.ext.findActiveMediaTab
import mozilla.components.feature.privatemode.notification.PrivateNotificationFeature import mozilla.components.feature.privatemode.notification.PrivateNotificationFeature
import mozilla.components.feature.search.BrowserStoreSearchAdapter import mozilla.components.feature.search.BrowserStoreSearchAdapter
@@ -144,6 +145,7 @@ import org.mozilla.fenix.perf.StartupPathProvider
import org.mozilla.fenix.perf.StartupTimeline import org.mozilla.fenix.perf.StartupTimeline
import org.mozilla.fenix.perf.StartupTypeTelemetry import org.mozilla.fenix.perf.StartupTypeTelemetry
import org.mozilla.fenix.session.PrivateNotificationService import org.mozilla.fenix.session.PrivateNotificationService
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.shortcut.NewTabShortcutIntentProcessor.Companion.ACTION_OPEN_PRIVATE_TAB import org.mozilla.fenix.shortcut.NewTabShortcutIntentProcessor.Companion.ACTION_OPEN_PRIVATE_TAB
import org.mozilla.fenix.tabhistory.TabHistoryDialogFragment import org.mozilla.fenix.tabhistory.TabHistoryDialogFragment
import org.mozilla.fenix.tabstray.TabsTrayFragment import org.mozilla.fenix.tabstray.TabsTrayFragment
@@ -193,6 +195,13 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
newTab = true, newTab = true,
from = BrowserDirection.FromGlobal, from = BrowserDirection.FromGlobal,
) )
} else {
startActivity(
SupportUtils.createCustomTabIntent(
context = this,
url = url,
),
)
} }
}, },
) )
@@ -406,10 +415,13 @@ open class HomeActivity : LocaleAwareAppCompatActivity(), NavHostActivity {
extensionsProcessDisabledForegroundController, extensionsProcessDisabledForegroundController,
extensionsProcessDisabledBackgroundController, extensionsProcessDisabledBackgroundController,
serviceWorkerSupport, serviceWorkerSupport,
webExtensionPromptFeature,
crashReporterBinding, crashReporterBinding,
) )
if (!isCustomTabIntent(intent)) {
lifecycle.addObserver(webExtensionPromptFeature)
}
if (shouldAddToRecentsScreen(intent)) { if (shouldAddToRecentsScreen(intent)) {
intent.removeExtra(START_IN_RECENTS_SCREEN) intent.removeExtra(START_IN_RECENTS_SCREEN)
moveTaskToBack(true) moveTaskToBack(true)

View File

@@ -34,6 +34,7 @@ import mozilla.components.ui.widgets.withCenterAlignedButtons
import org.mozilla.fenix.BuildConfig import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.theme.ThemeManager import org.mozilla.fenix.theme.ThemeManager
/** /**
@@ -272,6 +273,10 @@ class WebExtensionPromptFeature(
), ),
confirmButtonRadius = confirmButtonRadius =
(context.resources.getDimensionPixelSize(R.dimen.tab_corner_radius)).toFloat(), (context.resources.getDimensionPixelSize(R.dimen.tab_corner_radius)).toFloat(),
learnMoreLinkTextColor = ThemeManager.resolveAttribute(
R.attr.textAccent,
context,
),
), ),
onPositiveButtonClicked = { _, privateBrowsingAllowed -> onPositiveButtonClicked = { _, privateBrowsingAllowed ->
handlePermissions( handlePermissions(
@@ -287,6 +292,16 @@ class WebExtensionPromptFeature(
privateBrowsingAllowed = false, privateBrowsingAllowed = false,
) )
}, },
onLearnMoreClicked = {
onLinkClicked.invoke(
// Bug 1920564 - add finalized Learn More SUMO link for the install dialog
SupportUtils.getSumoURLForTopic(
context,
SupportUtils.SumoTopic.MANAGE_OPTIONAL_EXTENSION_PERMISSIONS,
),
false,
)
},
) )
dialog.show( dialog.show(
fragmentManager, fragmentManager,
@@ -312,6 +327,20 @@ class WebExtensionPromptFeature(
} }
} }
} }
dialog.onLearnMoreClicked = {
store.state.webExtensionPromptRequest?.let { promptRequest ->
if (promptRequest is WebExtensionPromptRequest.AfterInstallation.Permissions) {
onLinkClicked.invoke(
// Bug 1920564 - add finalized Learn More SUMO link for the install dialog
SupportUtils.getSumoURLForTopic(
context,
SupportUtils.SumoTopic.MANAGE_OPTIONAL_EXTENSION_PERMISSIONS,
),
false,
)
}
}
}
} }
findPreviousPostInstallationDialogFragment()?.let { dialog -> findPreviousPostInstallationDialogFragment()?.let { dialog ->

View File

@@ -16,6 +16,7 @@ import mozilla.components.browser.state.state.extension.WebExtensionPromptReques
import mozilla.components.browser.state.store.BrowserStore import mozilla.components.browser.state.store.BrowserStore
import mozilla.components.concept.engine.webextension.WebExtensionInstallException import mozilla.components.concept.engine.webextension.WebExtensionInstallException
import mozilla.components.feature.addons.Addon import mozilla.components.feature.addons.Addon
import mozilla.components.feature.addons.ui.PermissionsDialogFragment
import mozilla.components.support.ktx.android.content.appVersionName import mozilla.components.support.ktx.android.content.appVersionName
import mozilla.components.support.test.ext.joinBlocking import mozilla.components.support.test.ext.joinBlocking
import mozilla.components.support.test.robolectric.testContext import mozilla.components.support.test.robolectric.testContext
@@ -29,6 +30,7 @@ import org.junit.runner.RunWith
import org.mozilla.fenix.BuildConfig import org.mozilla.fenix.BuildConfig
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.helpers.FenixRobolectricTestRunner import org.mozilla.fenix.helpers.FenixRobolectricTestRunner
import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.utils.LinkTextView import org.mozilla.fenix.utils.LinkTextView
@RunWith(FenixRobolectricTestRunner::class) @RunWith(FenixRobolectricTestRunner::class)
@@ -390,4 +392,28 @@ class WebExtensionPromptFeatureTest {
dialog.dismiss() dialog.dismiss()
} }
} }
@Test
fun `WHEN clicking Learn More on the Permissions Dialog THEN open the correct SUMO page in a custom tab`() {
val addon: Addon = mockk(relaxed = true)
// Bug 1920564 - add finalized Learn More SUMO link for the install dialog
val expectedUrl = SupportUtils.getSumoURLForTopic(
testContext,
SupportUtils.SumoTopic.MANAGE_OPTIONAL_EXTENSION_PERMISSIONS,
)
val dialog = PermissionsDialogFragment.newInstance(
addon = addon,
forOptionalPermissions = false,
permissions = listOf("tabs"),
onLearnMoreClicked = {
onLinkClicked(expectedUrl, false)
},
)
dialog.onLearnMoreClicked?.invoke()
verify { onLinkClicked(expectedUrl, false) }
}
} }