From 9afe9679d84ae3db3bcac693f117642ec9d4cee2 Mon Sep 17 00:00:00 2001 From: Elise Richards Date: Thu, 17 Sep 2020 16:41:28 -0500 Subject: [PATCH] For #15079: handle QR permissions when changed in Android settings (#15097) * Define intent data for activity * Search dialog shows permissions for allow and deny camera * Check camera permissions for fxa pairing * Check camera permissions for old search * Tests for pairing sync interactor and controller. * Cleanup * Use bool pref for setting. Use interfaces and default implementations for the sync interactor and controller. * Lint --- .../mozilla/fenix/search/SearchFragment.kt | 127 ++++++++---------- .../searchdialog/SearchDialogFragment.kt | 61 ++++----- .../mozilla/fenix/settings/PairFragment.kt | 69 +--------- .../settings/account/DefaultSyncController.kt | 74 ++++++++++ .../settings/account/DefaultSyncInteractor.kt | 20 +++ .../settings/account/TurnOnSyncFragment.kt | 29 ++++ .../java/org/mozilla/fenix/utils/Settings.kt | 19 ++- app/src/main/res/values/preference_keys.xml | 2 +- .../fenix/search/SearchInteractorTest.kt | 9 ++ .../account/DefaultSyncControllerTest.kt | 40 ++++++ .../account/DefaultSyncInteractorTest.kt | 31 +++++ 11 files changed, 304 insertions(+), 177 deletions(-) create mode 100644 app/src/main/java/org/mozilla/fenix/settings/account/DefaultSyncController.kt create mode 100644 app/src/main/java/org/mozilla/fenix/settings/account/DefaultSyncInteractor.kt create mode 100644 app/src/test/java/org/mozilla/fenix/settings/account/DefaultSyncControllerTest.kt create mode 100644 app/src/test/java/org/mozilla/fenix/settings/account/DefaultSyncInteractorTest.kt diff --git a/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt b/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt index 0da68c01d..7076dbf70 100644 --- a/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/search/SearchFragment.kt @@ -26,7 +26,6 @@ import androidx.core.widget.NestedScrollView import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.navArgs -import androidx.preference.PreferenceManager import kotlinx.android.synthetic.main.fragment_search.* import kotlinx.android.synthetic.main.fragment_search.view.* import kotlinx.android.synthetic.main.search_suggestions_hint.view.* @@ -51,7 +50,6 @@ import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore import org.mozilla.fenix.components.searchengine.FenixSearchEngineProvider import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.getPreferenceKey import org.mozilla.fenix.ext.hideToolbar import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.settings @@ -67,7 +65,6 @@ class SearchFragment : Fragment(), UserInteractionHandler { private lateinit var toolbarView: ToolbarView private lateinit var awesomeBarView: AwesomeBarView private val qrFeature = ViewBoundFeatureWrapper() - private var permissionDidUpdate = false private lateinit var searchStore: SearchFragmentStore private lateinit var searchInteractor: SearchInteractor @@ -202,62 +199,26 @@ class SearchFragment : Fragment(), UserInteractionHandler { search_scan_button.visibility = if (context?.hasCamera() == true) View.VISIBLE else View.GONE qrFeature.set( - QrFeature( - requireContext(), - fragmentManager = parentFragmentManager, - onNeedToRequestPermissions = { permissions -> - requestPermissions(permissions, REQUEST_CODE_CAMERA_PERMISSIONS) - }, - onScanResult = { result -> - search_scan_button.isChecked = false - activity?.let { - AlertDialog.Builder(it).apply { - val spannable = resources.getSpanned( - R.string.qr_scanner_confirmation_dialog_message, - getString(R.string.app_name) to StyleSpan(BOLD), - result to StyleSpan(ITALIC) - ) - setMessage(spannable) - setNegativeButton(R.string.qr_scanner_dialog_negative) { dialog: DialogInterface, _ -> - requireComponents.analytics.metrics.track(Event.QRScannerNavigationDenied) - dialog.cancel() - resetFocus() - } - setPositiveButton(R.string.qr_scanner_dialog_positive) { dialog: DialogInterface, _ -> - requireComponents.analytics.metrics.track(Event.QRScannerNavigationAllowed) - (activity as HomeActivity) - .openToBrowserAndLoad( - searchTermOrURL = result, - newTab = searchStore.state.tabId == null, - from = BrowserDirection.FromSearch - ) - dialog.dismiss() - resetFocus() - } - create() - }.show() - requireComponents.analytics.metrics.track(Event.QRScannerPromptDisplayed) - } - }), + createQrFeature(), owner = this, view = view ) view.search_scan_button.setOnClickListener { - toolbarView.view.clearFocus() - - val cameraPermissionsDenied = PreferenceManager.getDefaultSharedPreferences(context) - .getBoolean( - getPreferenceKey(R.string.pref_key_camera_permissions), - false - ) - - if (cameraPermissionsDenied) { - searchInteractor.onCameraPermissionsNeeded() - } else { + if (requireContext().settings().shouldShowCameraPermissionPrompt) { requireComponents.analytics.metrics.track(Event.QRScannerOpened) qrFeature.get()?.scan(R.id.container) + } else { + if (requireContext().isPermissionGranted(Manifest.permission.CAMERA)) { + requireComponents.analytics.metrics.track(Event.QRScannerOpened) + qrFeature.get()?.scan(R.id.container) + } else { + searchInteractor.onCameraPermissionsNeeded() + } } + view.hideKeyboard() + search_scan_button.isChecked = false + requireContext().settings().setCameraPermissionNeededState = false } view.search_engines_shortcut_button.setOnClickListener { @@ -322,6 +283,47 @@ class SearchFragment : Fragment(), UserInteractionHandler { startPostponedEnterTransition() } + private fun createQrFeature(): QrFeature { + return QrFeature( + requireContext(), + fragmentManager = parentFragmentManager, + onNeedToRequestPermissions = { permissions -> + requestPermissions(permissions, REQUEST_CODE_CAMERA_PERMISSIONS) + }, + onScanResult = { result -> + search_scan_button.isChecked = false + activity?.let { + AlertDialog.Builder(it).apply { + val spannable = resources.getSpanned( + R.string.qr_scanner_confirmation_dialog_message, + getString(R.string.app_name) to StyleSpan(BOLD), + result to StyleSpan(ITALIC) + ) + setMessage(spannable) + setNegativeButton(R.string.qr_scanner_dialog_negative) { dialog: DialogInterface, _ -> + requireComponents.analytics.metrics.track(Event.QRScannerNavigationDenied) + dialog.cancel() + resetFocus() + } + setPositiveButton(R.string.qr_scanner_dialog_positive) { dialog: DialogInterface, _ -> + requireComponents.analytics.metrics.track(Event.QRScannerNavigationAllowed) + (activity as HomeActivity) + .openToBrowserAndLoad( + searchTermOrURL = result, + newTab = searchStore.state.tabId == null, + from = BrowserDirection.FromSearch + ) + dialog.dismiss() + resetFocus() + } + create() + }.show() + requireComponents.analytics.metrics.track(Event.QRScannerPromptDisplayed) + } + } + ) + } + private fun updateToolbarContentDescription(searchState: SearchFragmentState) { val urlView = toolbarView.view .findViewById(R.id.mozac_browser_toolbar_edit_url_view) @@ -352,16 +354,11 @@ class SearchFragment : Fragment(), UserInteractionHandler { searchStore.dispatch(SearchFragmentAction.UpdateShortcutsAvailability(areShortcutsAvailable)) } - if (!permissionDidUpdate) { - toolbarView.view.edit.focus() - } - updateClipboardSuggestion( searchStore.state, requireComponents.clipboardHandler.url ) - permissionDidUpdate = false hideToolbar() } @@ -423,22 +420,8 @@ class SearchFragment : Fragment(), UserInteractionHandler { when (requestCode) { REQUEST_CODE_CAMERA_PERMISSIONS -> qrFeature.withFeature { it.onPermissionsResult(permissions, grantResults) - - context?.let { context: Context -> - if (context.isPermissionGranted(Manifest.permission.CAMERA)) { - permissionDidUpdate = true - PreferenceManager.getDefaultSharedPreferences(context) - .edit().putBoolean( - getPreferenceKey(R.string.pref_key_camera_permissions), false - ).apply() - } else { - PreferenceManager.getDefaultSharedPreferences(context) - .edit().putBoolean( - getPreferenceKey(R.string.pref_key_camera_permissions), true - ).apply() - resetFocus() - } - } + resetFocus() + requireContext().settings().setCameraPermissionNeededState = false } else -> super.onRequestPermissionsResult(requestCode, permissions, grantResults) } diff --git a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt b/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt index ff56f62f2..5080db6d3 100644 --- a/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/searchdialog/SearchDialogFragment.kt @@ -29,7 +29,6 @@ import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.navigation.fragment.findNavController 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.view.* import kotlinx.android.synthetic.main.search_suggestions_hint.view.* @@ -54,7 +53,6 @@ import org.mozilla.fenix.components.searchengine.CustomSearchEngineStore import org.mozilla.fenix.components.searchengine.FenixSearchEngineProvider import org.mozilla.fenix.components.toolbar.ToolbarPosition import org.mozilla.fenix.ext.components -import org.mozilla.fenix.ext.getPreferenceKey import org.mozilla.fenix.ext.isKeyboardVisible import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.settings @@ -205,28 +203,34 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { interactor.onSearchShortcutsButtonClicked() } + qrFeature.set( + createQrFeature(), + owner = this, + view = view + ) + qr_scan_button.visibility = if (context?.hasCamera() == true) View.VISIBLE else View.GONE qr_scan_button.setOnClickListener { if (!requireContext().hasCamera()) { return@setOnClickListener } - + view.hideKeyboard() toolbarView.view.clearFocus() - 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 { + if (requireContext().settings().shouldShowCameraPermissionPrompt) { requireComponents.analytics.metrics.track(Event.QRScannerOpened) qrFeature.get()?.scan(R.id.search_wrapper) + } else { + if (requireContext().isPermissionGranted(Manifest.permission.CAMERA)) { + requireComponents.analytics.metrics.track(Event.QRScannerOpened) + qrFeature.get()?.scan(R.id.search_wrapper) + } else { + interactor.onCameraPermissionsNeeded() + resetFocus() + view.hideKeyboard() + toolbarView.view.requestFocus() + } } + requireContext().settings().setCameraPermissionNeededState = false } fill_link_from_clipboard.setOnClickListener { @@ -238,12 +242,6 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { ) } - qrFeature.set( - createQrFeature(), - owner = this, - view = view - ) - val stubListener = ViewStub.OnInflateListener { _, inflated -> inflated.learn_more.setOnClickListener { (activity as HomeActivity) @@ -379,7 +377,8 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { }.show() requireComponents.analytics.metrics.track(Event.QRScannerPromptDisplayed) } - }) + } + ) } override fun onRequestPermissionsResult( @@ -389,21 +388,9 @@ class SearchDialogFragment : AppCompatDialogFragment(), UserInteractionHandler { ) { 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() - } - } + it.onPermissionsResult(permissions, grantResults) + resetFocus() + requireContext().settings().setCameraPermissionNeededState = false } else -> super.onRequestPermissionsResult(requestCode, permissions, grantResults) } diff --git a/app/src/main/java/org/mozilla/fenix/settings/PairFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/PairFragment.kt index 224efa237..401a4e3b9 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/PairFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/PairFragment.kt @@ -4,28 +4,21 @@ package org.mozilla.fenix.settings -import android.content.DialogInterface -import android.content.Intent import android.content.pm.PackageManager import android.os.Build import android.os.Bundle import android.os.VibrationEffect import android.os.Vibrator -import android.provider.Settings -import android.text.SpannableString import android.view.View -import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.core.content.getSystemService import androidx.fragment.app.Fragment import androidx.navigation.fragment.NavHostFragment.findNavController import androidx.navigation.fragment.findNavController -import androidx.preference.PreferenceManager import mozilla.components.feature.qr.QrFeature import mozilla.components.support.base.feature.UserInteractionHandler import mozilla.components.support.base.feature.ViewBoundFeatureWrapper import org.mozilla.fenix.R -import org.mozilla.fenix.ext.getPreferenceKey import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.showToolbar @@ -65,23 +58,14 @@ class PairFragment : Fragment(R.layout.fragment_pair), UserInteractionHandler { false ) }, - scanMessage = R.string.pair_instructions_2), + scanMessage = R.string.pair_instructions_2 + ), owner = this, view = view ) - val cameraPermissionsDenied = PreferenceManager.getDefaultSharedPreferences(context) - .getBoolean( - getPreferenceKey(R.string.pref_key_camera_permissions), - false - ) - qrFeature.withFeature { - if (cameraPermissionsDenied) { - showPermissionsNeededDialog() - } else { - it.scan(R.id.pair_layout) - } + it.scan(R.id.pair_layout) } } @@ -116,57 +100,10 @@ class PairFragment : Fragment(R.layout.fragment_pair), UserInteractionHandler { qrFeature.withFeature { it.onPermissionsResult(permissions, grantResults) } - PreferenceManager.getDefaultSharedPreferences(context) - .edit().putBoolean( - getPreferenceKey(R.string.pref_key_camera_permissions), false - ).apply() } else { - PreferenceManager.getDefaultSharedPreferences(context) - .edit().putBoolean( - getPreferenceKey(R.string.pref_key_camera_permissions), true - ).apply() 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() - } } diff --git a/app/src/main/java/org/mozilla/fenix/settings/account/DefaultSyncController.kt b/app/src/main/java/org/mozilla/fenix/settings/account/DefaultSyncController.kt new file mode 100644 index 000000000..a44de10ea --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/settings/account/DefaultSyncController.kt @@ -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 org.mozilla.fenix.settings.account + +import android.content.DialogInterface +import android.content.Intent +import android.net.Uri +import android.os.Build +import android.provider.Settings +import android.text.SpannableString +import androidx.annotation.VisibleForTesting +import androidx.appcompat.app.AlertDialog +import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.R +import org.mozilla.fenix.settings.SupportUtils + +interface SyncController { + fun handleCameraPermissionsNeeded() +} + +/** + * Controller for handling [DefaultSyncInteractor] requests. + */ +class DefaultSyncController( + private val activity: HomeActivity +) : SyncController { + + /** + * 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(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() + } + } +} diff --git a/app/src/main/java/org/mozilla/fenix/settings/account/DefaultSyncInteractor.kt b/app/src/main/java/org/mozilla/fenix/settings/account/DefaultSyncInteractor.kt new file mode 100644 index 000000000..1b92682ed --- /dev/null +++ b/app/src/main/java/org/mozilla/fenix/settings/account/DefaultSyncInteractor.kt @@ -0,0 +1,20 @@ +/* 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 org.mozilla.fenix.settings.account + +interface SyncInteractor { + fun onCameraPermissionsNeeded() +} + +/** + * Interactor for [TurnOnSyncFragment]. + * + * @param syncController Handles the interactions + */ +class DefaultSyncInteractor(private val syncController: DefaultSyncController) : SyncInteractor { + override fun onCameraPermissionsNeeded() { + syncController.handleCameraPermissionsNeeded() + } +} diff --git a/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt b/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt index acfa74c2a..7f42aaf1d 100644 --- a/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt +++ b/app/src/main/java/org/mozilla/fenix/settings/account/TurnOnSyncFragment.kt @@ -4,6 +4,7 @@ package org.mozilla.fenix.settings.account +import android.Manifest import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -18,15 +19,21 @@ import mozilla.components.concept.sync.AccountObserver import mozilla.components.concept.sync.AuthType import mozilla.components.concept.sync.OAuthAccount import mozilla.components.support.ktx.android.content.hasCamera +import mozilla.components.support.ktx.android.content.isPermissionGranted +import mozilla.components.support.ktx.android.view.hideKeyboard +import org.mozilla.fenix.HomeActivity import org.mozilla.fenix.R import org.mozilla.fenix.components.FenixSnackbar import org.mozilla.fenix.components.metrics.Event import org.mozilla.fenix.ext.requireComponents +import org.mozilla.fenix.ext.settings import org.mozilla.fenix.ext.showToolbar class TurnOnSyncFragment : Fragment(), AccountObserver { private val args by navArgs() + private lateinit var interactor: DefaultSyncInteractor + private var shouldLoginJustWithEmail = false private var pairWithEmailStarted = false @@ -35,6 +42,23 @@ class TurnOnSyncFragment : Fragment(), AccountObserver { } private val paringClickListener = View.OnClickListener { + if (requireContext().settings().shouldShowCameraPermissionPrompt) { + requireComponents.analytics.metrics.track(Event.QRScannerOpened) + navigateToPairFragment() + } else { + if (requireContext().isPermissionGranted(Manifest.permission.CAMERA)) { + requireComponents.analytics.metrics.track(Event.QRScannerOpened) + navigateToPairFragment() + } else { + interactor.onCameraPermissionsNeeded() + view?.hideKeyboard() + } + } + view?.hideKeyboard() + requireContext().settings().setCameraPermissionNeededState = false + } + + private fun navigateToPairFragment() { val directions = TurnOnSyncFragmentDirections.actionTurnOnSyncFragmentToPairFragment() requireView().findNavController().navigate(directions) requireComponents.analytics.metrics.track(Event.SyncAuthScanPairing) @@ -89,6 +113,11 @@ class TurnOnSyncFragment : Fragment(), AccountObserver { getString(R.string.sign_in_instructions), HtmlCompat.FROM_HTML_MODE_LEGACY ) + + interactor = DefaultSyncInteractor( + DefaultSyncController(activity = activity as HomeActivity) + ) + return view } diff --git a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt index a5abe4cbc..713976beb 100644 --- a/app/src/main/java/org/mozilla/fenix/utils/Settings.kt +++ b/app/src/main/java/org/mozilla/fenix/utils/Settings.kt @@ -60,7 +60,6 @@ class Settings(private val appContext: Context) : PreferencesHolder { private const val ALLOWED_INT = 2 private const val CFR_COUNT_CONDITION_FOCUS_INSTALLED = 1 private const val CFR_COUNT_CONDITION_FOCUS_NOT_INSTALLED = 3 - private const val MIN_DAYS_SINCE_FEEDBACK_PROMPT = 120 const val ONE_DAY_MS = 60 * 60 * 24 * 1000L const val ONE_WEEK_MS = 60 * 60 * 24 * 7 * 1000L @@ -766,6 +765,24 @@ class Settings(private val appContext: Context) : PreferencesHolder { default = true ) + /** + * Used in [SearchDialogFragment.kt], [SearchFragment.kt] (deprecated), and [PairFragment.kt] + * to see if we need to check for camera permissions before using the QR code scanner. + */ + var shouldShowCameraPermissionPrompt by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_camera_permissions_needed), + default = true + ) + + /** + * Sets the state of permissions that have been checked, where [false] denotes already checked + * and [true] denotes needing to check. See [shouldShowCameraPermissionPrompt]. + */ + var setCameraPermissionNeededState by booleanPreference( + appContext.getPreferenceKey(R.string.pref_key_camera_permissions_needed), + default = true + ) + var shouldPromptToSaveLogins by booleanPreference( appContext.getPreferenceKey(R.string.pref_key_save_logins), default = true diff --git a/app/src/main/res/values/preference_keys.xml b/app/src/main/res/values/preference_keys.xml index 8139626ed..fd75d44f1 100644 --- a/app/src/main/res/values/preference_keys.xml +++ b/app/src/main/res/values/preference_keys.xml @@ -225,5 +225,5 @@ pref_key_close_tabs_after_one_week pref_key_close_tabs_after_one_month - pref_key_camera_permissions + pref_key_camera_permissions_needed diff --git a/app/src/test/java/org/mozilla/fenix/search/SearchInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/search/SearchInteractorTest.kt index 82b69a425..7a3af7ef0 100644 --- a/app/src/test/java/org/mozilla/fenix/search/SearchInteractorTest.kt +++ b/app/src/test/java/org/mozilla/fenix/search/SearchInteractorTest.kt @@ -106,4 +106,13 @@ class SearchInteractorTest { searchController.handleExistingSessionSelected(session) } } + + @Test + fun onCameraPermissionsNeeded() { + interactor.onCameraPermissionsNeeded() + + verify { + searchController.handleCameraPermissionsNeeded() + } + } } diff --git a/app/src/test/java/org/mozilla/fenix/settings/account/DefaultSyncControllerTest.kt b/app/src/test/java/org/mozilla/fenix/settings/account/DefaultSyncControllerTest.kt new file mode 100644 index 000000000..1d1424d01 --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/settings/account/DefaultSyncControllerTest.kt @@ -0,0 +1,40 @@ +/* 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 org.mozilla.fenix.settings.account + +import io.mockk.MockKAnnotations +import io.mockk.every +import io.mockk.impl.annotations.MockK +import io.mockk.mockk +import io.mockk.spyk +import io.mockk.verify +import org.junit.Before +import org.junit.Test +import org.mozilla.fenix.HomeActivity +import org.mozilla.fenix.search.AlertDialogBuilder + +class DefaultSyncControllerTest { + + private lateinit var syncController: DefaultSyncController + @MockK(relaxed = true) private lateinit var activity: HomeActivity + + @Before + fun setUp() { + MockKAnnotations.init(this) + syncController = DefaultSyncController(activity) + } + + @Test + fun `show camera permissions needed dialog`() { + val dialogBuilder: AlertDialogBuilder = mockk(relaxed = true) + + val spyController = spyk(syncController) + every { spyController.buildDialog() } returns dialogBuilder + + spyController.handleCameraPermissionsNeeded() + + verify { dialogBuilder.show() } + } +} diff --git a/app/src/test/java/org/mozilla/fenix/settings/account/DefaultSyncInteractorTest.kt b/app/src/test/java/org/mozilla/fenix/settings/account/DefaultSyncInteractorTest.kt new file mode 100644 index 000000000..2469a655a --- /dev/null +++ b/app/src/test/java/org/mozilla/fenix/settings/account/DefaultSyncInteractorTest.kt @@ -0,0 +1,31 @@ +/* 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 org.mozilla.fenix.settings.account + +import io.mockk.mockk +import io.mockk.verify +import org.junit.Before +import org.junit.Test + +class DefaultSyncInteractorTest { + + private lateinit var syncInteractor: DefaultSyncInteractor + private lateinit var syncController: DefaultSyncController + + @Before + fun setUp() { + syncController = mockk(relaxed = true) + syncInteractor = DefaultSyncInteractor(syncController) + } + + @Test + fun onCameraPermissionsNeeded() { + syncInteractor.onCameraPermissionsNeeded() + + verify { + syncController.handleCameraPermissionsNeeded() + } + } +}