2
0
mirror of https://github.com/fork-maintainers/iceraven-browser synced 2024-11-17 15:26:23 +00:00
* Show dialog when permissions are denied

* Add qr permissions dialog to search dialog fragment

* Add qr permissions dialog to the pairing screen

* Show dialog after permissions have been denied

* Reset focus after denying permissions

* Show dialog after permissions denied in search frag and par frag

* Use shared preferences to store camera permission state

* Move dialog creation into the search controller and add tests

* Dialog controller implementation and test

* Route to intent with correct activity. Set focus when dismissing dialog

* Get preferences in old search
This commit is contained in:
Elise Richards 2020-09-10 19:09:38 -05:00 committed by GitHub
parent a03cffe60a
commit 892a22f6f5
11 changed files with 310 additions and 19 deletions

View File

@ -4,7 +4,13 @@
package org.mozilla.fenix.search package org.mozilla.fenix.search
import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Build
import android.text.SpannableString
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AlertDialog
import androidx.navigation.NavController import androidx.navigation.NavController
import mozilla.components.browser.search.SearchEngine import mozilla.components.browser.search.SearchEngine
import mozilla.components.browser.session.Session import mozilla.components.browser.session.Session
@ -29,6 +35,7 @@ import org.mozilla.fenix.utils.Settings
/** /**
* An interface that handles the view manipulation of the Search, triggered by the Interactor * An interface that handles the view manipulation of the Search, triggered by the Interactor
*/ */
@Suppress("TooManyFunctions")
interface SearchController { interface SearchController {
fun handleUrlCommitted(url: String) fun handleUrlCommitted(url: String)
fun handleEditingCancelled() fun handleEditingCancelled()
@ -40,6 +47,7 @@ interface SearchController {
fun handleExistingSessionSelected(session: Session) fun handleExistingSessionSelected(session: Session)
fun handleExistingSessionSelected(tabId: String) fun handleExistingSessionSelected(tabId: String)
fun handleSearchShortcutsButtonClicked() fun handleSearchShortcutsButtonClicked()
fun handleCameraPermissionsNeeded()
} }
@Suppress("TooManyFunctions", "LongParameterList") @Suppress("TooManyFunctions", "LongParameterList")
@ -194,4 +202,51 @@ class DefaultSearchController(
handleExistingSessionSelected(session) handleExistingSessionSelected(session)
} }
} }
/**
* Creates and shows an [AlertDialog] when camera permissions are needed.
*
* In versions above M, [AlertDialog.BUTTON_POSITIVE] takes the user to the app settings. This
* intent only exists in M and above. Below M, [AlertDialog.BUTTON_POSITIVE] routes to a SUMO
* help page to find the app settings.
*
* [AlertDialog.BUTTON_NEGATIVE] dismisses the dialog.
*/
override fun handleCameraPermissionsNeeded() {
val dialog = buildDialog()
dialog.show()
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun buildDialog(): AlertDialog.Builder {
return AlertDialog.Builder(activity).apply {
val spannableText = SpannableString(
activity.resources.getString(R.string.camera_permissions_needed_message)
)
setMessage(spannableText)
setNegativeButton(R.string.camera_permissions_needed_negative_button_text) {
dialog: DialogInterface, _ ->
dialog.cancel()
}
setPositiveButton(R.string.camera_permissions_needed_positive_button_text) {
dialog: DialogInterface, _ ->
val intent: Intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
} else {
SupportUtils.createCustomTabIntent(
activity,
SupportUtils.getSumoURLForTopic(
activity,
SupportUtils.SumoTopic.QR_CAMERA_ACCESS
)
)
}
val uri = Uri.fromParts("package", activity.packageName, null)
intent.data = uri
dialog.cancel()
activity.startActivity(intent)
}
create()
}
}
} }

View File

@ -26,6 +26,7 @@ import androidx.core.widget.NestedScrollView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.preference.PreferenceManager
import kotlinx.android.synthetic.main.fragment_search.* import kotlinx.android.synthetic.main.fragment_search.*
import kotlinx.android.synthetic.main.fragment_search.view.* import kotlinx.android.synthetic.main.fragment_search.view.*
import kotlinx.android.synthetic.main.search_suggestions_hint.view.* import kotlinx.android.synthetic.main.search_suggestions_hint.view.*
@ -50,6 +51,7 @@ import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore
import org.mozilla.fenix.components.searchengine.FenixSearchEngineProvider import org.mozilla.fenix.components.searchengine.FenixSearchEngineProvider
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.ext.hideToolbar import org.mozilla.fenix.ext.hideToolbar
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
@ -219,6 +221,7 @@ class SearchFragment : Fragment(), UserInteractionHandler {
setNegativeButton(R.string.qr_scanner_dialog_negative) { dialog: DialogInterface, _ -> setNegativeButton(R.string.qr_scanner_dialog_negative) { dialog: DialogInterface, _ ->
requireComponents.analytics.metrics.track(Event.QRScannerNavigationDenied) requireComponents.analytics.metrics.track(Event.QRScannerNavigationDenied)
dialog.cancel() dialog.cancel()
resetFocus()
} }
setPositiveButton(R.string.qr_scanner_dialog_positive) { dialog: DialogInterface, _ -> setPositiveButton(R.string.qr_scanner_dialog_positive) { dialog: DialogInterface, _ ->
requireComponents.analytics.metrics.track(Event.QRScannerNavigationAllowed) requireComponents.analytics.metrics.track(Event.QRScannerNavigationAllowed)
@ -229,6 +232,7 @@ class SearchFragment : Fragment(), UserInteractionHandler {
from = BrowserDirection.FromSearch from = BrowserDirection.FromSearch
) )
dialog.dismiss() dialog.dismiss()
resetFocus()
} }
create() create()
}.show() }.show()
@ -241,8 +245,19 @@ class SearchFragment : Fragment(), UserInteractionHandler {
view.search_scan_button.setOnClickListener { view.search_scan_button.setOnClickListener {
toolbarView.view.clearFocus() toolbarView.view.clearFocus()
requireComponents.analytics.metrics.track(Event.QRScannerOpened)
qrFeature.get()?.scan(R.id.container) val cameraPermissionsDenied = PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean(
getPreferenceKey(R.string.pref_key_camera_permissions),
false
)
if (cameraPermissionsDenied) {
searchInteractor.onCameraPermissionsNeeded()
} else {
requireComponents.analytics.metrics.track(Event.QRScannerOpened)
qrFeature.get()?.scan(R.id.container)
}
} }
view.search_engines_shortcut_button.setOnClickListener { view.search_engines_shortcut_button.setOnClickListener {
@ -368,15 +383,19 @@ class SearchFragment : Fragment(), UserInteractionHandler {
override fun onBackPressed(): Boolean { override fun onBackPressed(): Boolean {
return when { return when {
qrFeature.onBackPressed() -> { qrFeature.onBackPressed() -> {
toolbarView.view.edit.focus() resetFocus()
view?.search_scan_button?.isChecked = false
toolbarView.view.requestFocus()
true true
} }
else -> false else -> false
} }
} }
private fun resetFocus() {
search_scan_button.isChecked = false
toolbarView.view.edit.focus()
toolbarView.view.requestFocus()
}
private fun updateSearchWithLabel(searchState: SearchFragmentState) { private fun updateSearchWithLabel(searchState: SearchFragmentState) {
search_engine_shortcut.visibility = search_engine_shortcut.visibility =
if (searchState.showSearchShortcuts) View.VISIBLE else View.GONE if (searchState.showSearchShortcuts) View.VISIBLE else View.GONE
@ -408,8 +427,16 @@ class SearchFragment : Fragment(), UserInteractionHandler {
context?.let { context: Context -> context?.let { context: Context ->
if (context.isPermissionGranted(Manifest.permission.CAMERA)) { if (context.isPermissionGranted(Manifest.permission.CAMERA)) {
permissionDidUpdate = true permissionDidUpdate = true
PreferenceManager.getDefaultSharedPreferences(context)
.edit().putBoolean(
getPreferenceKey(R.string.pref_key_camera_permissions), false
).apply()
} else { } else {
view?.search_scan_button?.isChecked = false PreferenceManager.getDefaultSharedPreferences(context)
.edit().putBoolean(
getPreferenceKey(R.string.pref_key_camera_permissions), true
).apply()
resetFocus()
} }
} }
} }

View File

@ -13,6 +13,7 @@ import org.mozilla.fenix.search.toolbar.ToolbarInteractor
* Interactor for the search screen * Interactor for the search screen
* Provides implementations for the AwesomeBarView and ToolbarView * Provides implementations for the AwesomeBarView and ToolbarView
*/ */
@Suppress("TooManyFunctions")
class SearchInteractor( class SearchInteractor(
private val searchController: SearchController private val searchController: SearchController
) : AwesomeBarInteractor, ToolbarInteractor { ) : AwesomeBarInteractor, ToolbarInteractor {
@ -56,4 +57,8 @@ class SearchInteractor(
override fun onExistingSessionSelected(tabId: String) { override fun onExistingSessionSelected(tabId: String) {
searchController.handleExistingSessionSelected(tabId) searchController.handleExistingSessionSelected(tabId)
} }
fun onCameraPermissionsNeeded() {
searchController.handleCameraPermissionsNeeded()
}
} }

View File

@ -4,7 +4,13 @@
package org.mozilla.fenix.searchdialog package org.mozilla.fenix.searchdialog
import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.net.Uri
import android.os.Build
import android.text.SpannableString
import androidx.annotation.VisibleForTesting
import androidx.appcompat.app.AlertDialog
import androidx.navigation.NavController import androidx.navigation.NavController
import mozilla.components.browser.search.SearchEngine import mozilla.components.browser.search.SearchEngine
import mozilla.components.browser.session.Session import mozilla.components.browser.session.Session
@ -186,4 +192,50 @@ class SearchDialogController(
handleExistingSessionSelected(session) handleExistingSessionSelected(session)
} }
} }
/**
* Creates and shows an [AlertDialog] when camera permissions are needed.
*
* In versions above M, [AlertDialog.BUTTON_POSITIVE] takes the user to the app settings. This
* intent only exists in M and above. Below M, [AlertDialog.BUTTON_POSITIVE] routes to a SUMO
* help page to find the app settings.
*
* [AlertDialog.BUTTON_NEGATIVE] dismisses the dialog.
*/
override fun handleCameraPermissionsNeeded() {
val dialog = buildDialog()
dialog.show()
}
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
fun buildDialog(): AlertDialog.Builder {
return AlertDialog.Builder(activity).apply {
val spannableText = SpannableString(
activity.resources.getString(R.string.camera_permissions_needed_message)
)
setMessage(spannableText)
setNegativeButton(R.string.camera_permissions_needed_negative_button_text) { _, _ ->
dismissDialog()
}
setPositiveButton(R.string.camera_permissions_needed_positive_button_text) {
dialog: DialogInterface, _ ->
val intent: Intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
} else {
SupportUtils.createCustomTabIntent(
activity,
SupportUtils.getSumoURLForTopic(
activity,
SupportUtils.SumoTopic.QR_CAMERA_ACCESS
)
)
}
val uri = Uri.fromParts("package", activity.packageName, null)
intent.data = uri
dialog.cancel()
activity.startActivity(intent)
}
create()
}
}
} }

View File

@ -4,6 +4,7 @@
package org.mozilla.fenix.searchdialog package org.mozilla.fenix.searchdialog
import android.Manifest
import android.app.Activity import android.app.Activity
import android.app.Dialog import android.app.Dialog
import android.content.Context import android.content.Context
@ -28,11 +29,8 @@ import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
import androidx.preference.PreferenceManager
import kotlinx.android.synthetic.main.fragment_search_dialog.* import kotlinx.android.synthetic.main.fragment_search_dialog.*
import kotlinx.android.synthetic.main.fragment_search_dialog.fill_link_from_clipboard
import kotlinx.android.synthetic.main.fragment_search_dialog.pill_wrapper
import kotlinx.android.synthetic.main.fragment_search_dialog.qr_scan_button
import kotlinx.android.synthetic.main.fragment_search_dialog.toolbar
import kotlinx.android.synthetic.main.fragment_search_dialog.view.* import kotlinx.android.synthetic.main.fragment_search_dialog.view.*
import kotlinx.android.synthetic.main.search_suggestions_hint.view.* import kotlinx.android.synthetic.main.search_suggestions_hint.view.*
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -44,6 +42,7 @@ import mozilla.components.support.base.feature.UserInteractionHandler
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import mozilla.components.support.ktx.android.content.getColorFromAttr import mozilla.components.support.ktx.android.content.getColorFromAttr
import mozilla.components.support.ktx.android.content.hasCamera import mozilla.components.support.ktx.android.content.hasCamera
import mozilla.components.support.ktx.android.content.isPermissionGranted
import mozilla.components.support.ktx.android.content.res.getSpanned import mozilla.components.support.ktx.android.content.res.getSpanned
import mozilla.components.support.ktx.android.view.hideKeyboard import mozilla.components.support.ktx.android.view.hideKeyboard
import mozilla.components.ui.autocomplete.InlineAutocompleteEditText import mozilla.components.ui.autocomplete.InlineAutocompleteEditText
@ -55,6 +54,7 @@ import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore
import org.mozilla.fenix.components.searchengine.FenixSearchEngineProvider import org.mozilla.fenix.components.searchengine.FenixSearchEngineProvider
import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.components.toolbar.ToolbarPosition
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.settings
import org.mozilla.fenix.search.SearchFragmentAction import org.mozilla.fenix.search.SearchFragmentAction
@ -204,8 +204,22 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
if (!requireContext().hasCamera()) { return@setOnClickListener } if (!requireContext().hasCamera()) { return@setOnClickListener }
toolbarView.view.clearFocus() toolbarView.view.clearFocus()
requireComponents.analytics.metrics.track(Event.QRScannerOpened)
qrFeature.get()?.scan(R.id.search_wrapper) val cameraPermissionsDenied =
PreferenceManager.getDefaultSharedPreferences(context).getBoolean(
getPreferenceKey(R.string.pref_key_camera_permissions),
false
)
if (cameraPermissionsDenied) {
interactor.onCameraPermissionsNeeded()
resetFocus()
view.hideKeyboard()
toolbarView.view.requestFocus()
} else {
requireComponents.analytics.metrics.track(Event.QRScannerOpened)
qrFeature.get()?.scan(R.id.search_wrapper)
}
} }
fill_link_from_clipboard.setOnClickListener { fill_link_from_clipboard.setOnClickListener {
@ -280,6 +294,19 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
} }
} }
override fun onResume() {
super.onResume()
resetFocus()
toolbarView.view.edit.focus()
}
override fun onPause() {
super.onPause()
qr_scan_button.isChecked = false
view?.hideKeyboard()
toolbarView.view.requestFocus()
}
override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) { override fun onActivityResult(requestCode: Int, resultCode: Int, intent: Intent?) {
if (requestCode == VoiceSearchActivity.SPEECH_REQUEST_CODE && resultCode == Activity.RESULT_OK) { if (requestCode == VoiceSearchActivity.SPEECH_REQUEST_CODE && resultCode == Activity.RESULT_OK) {
intent?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)?.first()?.also { intent?.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS)?.first()?.also {
@ -293,9 +320,7 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
override fun onBackPressed(): Boolean { override fun onBackPressed(): Boolean {
return when { return when {
qrFeature.onBackPressed() -> { qrFeature.onBackPressed() -> {
toolbarView.view.edit.focus() resetFocus()
view?.qr_scan_button?.isChecked = false
toolbarView.view.requestFocus()
true true
} }
else -> { else -> {
@ -350,6 +375,39 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler {
}) })
} }
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
when (requestCode) {
REQUEST_CODE_CAMERA_PERMISSIONS -> qrFeature.withFeature {
context?.let { context: Context ->
it.onPermissionsResult(permissions, grantResults)
if (!context.isPermissionGranted(Manifest.permission.CAMERA)) {
PreferenceManager.getDefaultSharedPreferences(context)
.edit().putBoolean(
getPreferenceKey(R.string.pref_key_camera_permissions), true
).apply()
resetFocus()
} else {
PreferenceManager.getDefaultSharedPreferences(context)
.edit().putBoolean(
getPreferenceKey(R.string.pref_key_camera_permissions), false
).apply()
}
}
}
else -> super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
}
private fun resetFocus() {
qr_scan_button.isChecked = false
toolbarView.view.edit.focus()
toolbarView.view.requestFocus()
}
private fun setupConstraints(view: View) { private fun setupConstraints(view: View) {
if (view.context.settings().toolbarPosition == ToolbarPosition.BOTTOM) { if (view.context.settings().toolbarPosition == ToolbarPosition.BOTTOM) {
ConstraintSet().apply { ConstraintSet().apply {

View File

@ -4,27 +4,35 @@
package org.mozilla.fenix.settings package org.mozilla.fenix.settings
import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.VibrationEffect import android.os.VibrationEffect
import android.os.Vibrator import android.os.Vibrator
import android.provider.Settings
import android.text.SpannableString
import android.view.View import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.navigation.fragment.NavHostFragment.findNavController import androidx.navigation.fragment.NavHostFragment.findNavController
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.preference.PreferenceManager
import mozilla.components.feature.qr.QrFeature import mozilla.components.feature.qr.QrFeature
import mozilla.components.support.base.feature.UserInteractionHandler import mozilla.components.support.base.feature.UserInteractionHandler
import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import mozilla.components.support.base.feature.ViewBoundFeatureWrapper
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.showToolbar import org.mozilla.fenix.ext.showToolbar
class PairFragment : Fragment(R.layout.fragment_pair), UserInteractionHandler { class PairFragment : Fragment(R.layout.fragment_pair), UserInteractionHandler {
private val qrFeature = ViewBoundFeatureWrapper<QrFeature>() private val qrFeature = ViewBoundFeatureWrapper<QrFeature>()
private val preferences = PreferenceManager.getDefaultSharedPreferences(context)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
@ -63,8 +71,17 @@ class PairFragment : Fragment(R.layout.fragment_pair), UserInteractionHandler {
view = view view = view
) )
val cameraPermissionsDenied = preferences.getBoolean(
getPreferenceKey(R.string.pref_key_camera_permissions),
false
)
qrFeature.withFeature { qrFeature.withFeature {
it.scan(R.id.pair_layout) if (cameraPermissionsDenied) {
showPermissionsNeededDialog()
} else {
it.scan(R.id.pair_layout)
}
} }
} }
@ -99,10 +116,55 @@ class PairFragment : Fragment(R.layout.fragment_pair), UserInteractionHandler {
qrFeature.withFeature { qrFeature.withFeature {
it.onPermissionsResult(permissions, grantResults) it.onPermissionsResult(permissions, grantResults)
} }
preferences.edit().putBoolean(
getPreferenceKey(R.string.pref_key_camera_permissions), false
).apply()
} else { } else {
preferences.edit().putBoolean(
getPreferenceKey(R.string.pref_key_camera_permissions), true
).apply()
findNavController().popBackStack(R.id.turnOnSyncFragment, false) findNavController().popBackStack(R.id.turnOnSyncFragment, false)
} }
} }
} }
} }
/**
* Shows an [AlertDialog] when camera permissions are needed.
*
* In versions above M, [AlertDialog.BUTTON_POSITIVE] takes the user to the app settings. This
* intent only exists in M and above. Below M, [AlertDialog.BUTTON_POSITIVE] routes to a SUMO
* help page to find the app settings.
*
* [AlertDialog.BUTTON_NEGATIVE] dismisses the dialog.
*/
private fun showPermissionsNeededDialog() {
AlertDialog.Builder(requireContext()).apply {
val spannableText = SpannableString(
resources.getString(R.string.camera_permissions_needed_message)
)
setMessage(spannableText)
setNegativeButton(R.string.camera_permissions_needed_negative_button_text) {
dialog: DialogInterface, _ ->
dialog.cancel()
}
setPositiveButton(R.string.camera_permissions_needed_positive_button_text) {
dialog: DialogInterface, _ ->
val intent: Intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
} else {
SupportUtils.createCustomTabIntent(
requireContext(),
SupportUtils.getSumoURLForTopic(
requireContext(),
SupportUtils.SumoTopic.QR_CAMERA_ACCESS
)
)
}
dialog.cancel()
startActivity(intent)
}
create()
}.show()
}
} }

View File

@ -40,7 +40,8 @@ object SupportUtils {
SEARCH_SUGGESTION("how-search-firefox-preview"), SEARCH_SUGGESTION("how-search-firefox-preview"),
CUSTOM_SEARCH_ENGINES("custom-search-engines"), CUSTOM_SEARCH_ENGINES("custom-search-engines"),
UPGRADE_FAQ("firefox-preview-upgrade-faqs"), UPGRADE_FAQ("firefox-preview-upgrade-faqs"),
SYNC_SETUP("how-set-firefox-sync-firefox-preview") SYNC_SETUP("how-set-firefox-sync-firefox-preview"),
QR_CAMERA_ACCESS("qr-camera-access")
} }
enum class MozillaPage(internal val path: String) { enum class MozillaPage(internal val path: String) {

View File

@ -222,4 +222,6 @@
<string name="pref_key_close_tabs_after_one_day" translatable="false">pref_key_close_tabs_after_one_day</string> <string name="pref_key_close_tabs_after_one_day" translatable="false">pref_key_close_tabs_after_one_day</string>
<string name="pref_key_close_tabs_after_one_week" translatable="false">pref_key_close_tabs_after_one_week</string> <string name="pref_key_close_tabs_after_one_week" translatable="false">pref_key_close_tabs_after_one_week</string>
<string name="pref_key_close_tabs_after_one_month" translatable="false">pref_key_close_tabs_after_one_month</string> <string name="pref_key_close_tabs_after_one_month" translatable="false">pref_key_close_tabs_after_one_month</string>
<string name="pref_key_camera_permissions" translatable="false">pref_key_camera_permissions</string>
</resources> </resources>

View File

@ -1543,7 +1543,7 @@
<!-- Content description for close button in collection placeholder. --> <!-- Content description for close button in collection placeholder. -->
<string name="remove_home_collection_placeholder_content_description">Remove</string> <string name="remove_home_collection_placeholder_content_description">Remove</string>
<!-- depcrecated: text for the firefox account onboarding card header <!-- Deprecated: text for the firefox account onboarding card header
The first parameter is the name of the app (e.g. Firefox Preview) --> The first parameter is the name of the app (e.g. Firefox Preview) -->
<string name="onboarding_firefox_account_header">Get the most out of %s.</string> <string name="onboarding_firefox_account_header">Get the most out of %s.</string>

View File

@ -4,6 +4,7 @@
package org.mozilla.fenix.search package org.mozilla.fenix.search
import androidx.appcompat.app.AlertDialog
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
import io.mockk.MockKAnnotations import io.mockk.MockKAnnotations
@ -13,6 +14,7 @@ import io.mockk.impl.annotations.MockK
import io.mockk.just import io.mockk.just
import io.mockk.mockk import io.mockk.mockk
import io.mockk.mockkObject import io.mockk.mockkObject
import io.mockk.spyk
import io.mockk.unmockkObject import io.mockk.unmockkObject
import io.mockk.verify import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -32,6 +34,8 @@ import org.mozilla.fenix.components.metrics.MetricsUtils
import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
typealias AlertDialogBuilder = AlertDialog.Builder
@ExperimentalCoroutinesApi @ExperimentalCoroutinesApi
class DefaultSearchControllerTest { class DefaultSearchControllerTest {
@ -58,7 +62,6 @@ class DefaultSearchControllerTest {
every { id } returns R.id.searchFragment every { id } returns R.id.searchFragment
} }
every { MetricsUtils.createSearchEvent(searchEngine, activity, any()) } returns null every { MetricsUtils.createSearchEvent(searchEngine, activity, any()) } returns null
controller = DefaultSearchController( controller = DefaultSearchController(
activity = activity, activity = activity,
sessionManager = sessionManager, sessionManager = sessionManager,
@ -328,4 +331,16 @@ class DefaultSearchControllerTest {
verify { sessionManager.select(any()) } verify { sessionManager.select(any()) }
verify { activity.openToBrowser(from = BrowserDirection.FromSearch) } verify { activity.openToBrowser(from = BrowserDirection.FromSearch) }
} }
@Test
fun `show camera permissions needed dialog`() {
val dialogBuilder: AlertDialogBuilder = mockk(relaxed = true)
val spyController = spyk(controller)
every { spyController.buildDialog() } returns dialogBuilder
spyController.handleCameraPermissionsNeeded()
verify { dialogBuilder.show() }
}
} }

View File

@ -13,6 +13,7 @@ import io.mockk.impl.annotations.MockK
import io.mockk.just import io.mockk.just
import io.mockk.mockk import io.mockk.mockk
import io.mockk.mockkObject import io.mockk.mockkObject
import io.mockk.spyk
import io.mockk.unmockkObject import io.mockk.unmockkObject
import io.mockk.verify import io.mockk.verify
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
@ -29,6 +30,7 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.components.metrics.MetricController import org.mozilla.fenix.components.metrics.MetricController
import org.mozilla.fenix.components.metrics.MetricsUtils import org.mozilla.fenix.components.metrics.MetricsUtils
import org.mozilla.fenix.search.AlertDialogBuilder
import org.mozilla.fenix.search.SearchFragmentAction import org.mozilla.fenix.search.SearchFragmentAction
import org.mozilla.fenix.settings.SupportUtils import org.mozilla.fenix.settings.SupportUtils
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
@ -342,4 +344,16 @@ class SearchDialogControllerTest {
verify { sessionManager.select(any()) } verify { sessionManager.select(any()) }
verify { activity.openToBrowser(from = BrowserDirection.FromSearchDialog) } verify { activity.openToBrowser(from = BrowserDirection.FromSearchDialog) }
} }
@Test
fun `show camera permissions needed dialog`() {
val dialogBuilder: AlertDialogBuilder = mockk(relaxed = true)
val spyController = spyk(controller)
every { spyController.buildDialog() } returns dialogBuilder
spyController.handleCameraPermissionsNeeded()
verify { dialogBuilder.show() }
}
} }