1
0
Fork 0

For #5547 - Add top level auth when accessing passwords

master
Emily Kager 2019-10-29 17:14:12 -07:00 committed by Emily Kager
parent 218763f9be
commit 3e2b88cc91
11 changed files with 329 additions and 12 deletions

View File

@ -405,6 +405,7 @@ dependencies {
debugImplementation Deps.leakcanary debugImplementation Deps.leakcanary
implementation Deps.androidx_legacy implementation Deps.androidx_legacy
implementation Deps.androidx_biometric
implementation Deps.androidx_paging implementation Deps.androidx_paging
implementation Deps.androidx_preference implementation Deps.androidx_preference
implementation Deps.androidx_fragment implementation Deps.androidx_fragment

View File

@ -12,6 +12,7 @@
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" /> <uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/> <uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
<uses-permission android:name="android.permission.USE_BIOMETRIC" android:requiredFeature="false"/>
<!-- Needed to prompt the user to give permission to install a downloaded apk --> <!-- Needed to prompt the user to give permission to install a downloaded apk -->
<uses-permission-sdk-23 android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission-sdk-23 android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

View File

@ -9,6 +9,7 @@ import android.text.InputType
import android.view.View import android.view.View
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import kotlinx.android.synthetic.main.fragment_saved_login_site_info.* import kotlinx.android.synthetic.main.fragment_saved_login_site_info.*
import org.mozilla.fenix.R import org.mozilla.fenix.R
@ -24,6 +25,12 @@ class SavedLoginSiteInfoFragment : Fragment(R.layout.fragment_saved_login_site_i
).savedLoginItem ).savedLoginItem
} }
override fun onPause() {
// If we pause this fragment, we want to pop users back to reauth
findNavController().popBackStack(R.id.loginsFragment, false)
super.onPause()
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)

View File

@ -63,6 +63,12 @@ class SavedLoginsFragment : Fragment() {
} }
} }
override fun onPause() {
// If we pause this fragment, we want to pop users back to reauth
findNavController().popBackStack(R.id.loginsFragment, false)
super.onPause()
}
private fun itemClicked(item: SavedLoginsItem) { private fun itemClicked(item: SavedLoginsItem) {
val directions = val directions =
SavedLoginsFragmentDirections.actionSavedLoginsFragmentToSavedLoginSiteInfoFragment(item) SavedLoginsFragmentDirections.actionSavedLoginsFragmentToSavedLoginSiteInfoFragment(item)

View File

@ -4,11 +4,25 @@
package org.mozilla.fenix.settings package org.mozilla.fenix.settings
import android.annotation.TargetApi
import android.app.Activity.RESULT_OK
import android.app.KeyguardManager
import android.content.Context.KEYGUARD_SERVICE
import android.content.DialogInterface
import android.content.Intent
import android.os.Build
import android.os.Build.VERSION_CODES.M
import android.os.Bundle import android.os.Bundle
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.biometric.BiometricManager
import androidx.biometric.BiometricPrompt
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceFragmentCompat
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
import mozilla.components.concept.sync.AccountObserver import mozilla.components.concept.sync.AccountObserver
import mozilla.components.concept.sync.AuthType import mozilla.components.concept.sync.AuthType
import mozilla.components.concept.sync.OAuthAccount import mozilla.components.concept.sync.OAuthAccount
@ -17,6 +31,8 @@ import mozilla.components.service.fxa.manager.SyncEnginesStorage
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.getPreferenceKey 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 java.util.concurrent.Executors
@Suppress("TooManyFunctions") @Suppress("TooManyFunctions")
class LoginsFragment : PreferenceFragmentCompat(), AccountObserver { class LoginsFragment : PreferenceFragmentCompat(), AccountObserver {
@ -31,7 +47,11 @@ class LoginsFragment : PreferenceFragmentCompat(), AccountObserver {
val savedLoginsKey = getPreferenceKey(R.string.pref_key_saved_logins) val savedLoginsKey = getPreferenceKey(R.string.pref_key_saved_logins)
findPreference<Preference>(savedLoginsKey)?.setOnPreferenceClickListener { findPreference<Preference>(savedLoginsKey)?.setOnPreferenceClickListener {
navigateToLoginsSettingsFragment() if (Build.VERSION.SDK_INT >= M && isHardwareAvailable && hasBiometricEnrolled) {
showBiometricPrompt()
} else {
verifyPinOrShowSetupWarning()
}
true true
} }
@ -54,6 +74,31 @@ class LoginsFragment : PreferenceFragmentCompat(), AccountObserver {
override fun onAuthenticationProblems() = updateSyncPreferenceNeedsReauth() override fun onAuthenticationProblems() = updateSyncPreferenceNeedsReauth()
val isHardwareAvailable: Boolean by lazy {
if (Build.VERSION.SDK_INT >= M) {
context?.let {
val bm = BiometricManager.from(it)
val canAuthenticate = bm.canAuthenticate()
!(canAuthenticate == BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE ||
canAuthenticate == BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE)
} ?: false
} else {
false
}
}
val hasBiometricEnrolled: Boolean by lazy {
if (Build.VERSION.SDK_INT >= M) {
context?.let {
val bm = BiometricManager.from(it)
val canAuthenticate = bm.canAuthenticate()
(canAuthenticate == BiometricManager.BIOMETRIC_SUCCESS)
} ?: false
} else {
false
}
}
private fun updateSyncPreferenceStatus() { private fun updateSyncPreferenceStatus() {
val syncLogins = getPreferenceKey(R.string.pref_key_password_sync_logins) val syncLogins = getPreferenceKey(R.string.pref_key_password_sync_logins)
findPreference<Preference>(syncLogins)?.apply { findPreference<Preference>(syncLogins)?.apply {
@ -92,7 +137,93 @@ class LoginsFragment : PreferenceFragmentCompat(), AccountObserver {
} }
} }
private fun navigateToLoginsSettingsFragment() { @TargetApi(M)
private fun showBiometricPrompt() {
val biometricPromptCallback = object : BiometricPrompt.AuthenticationCallback() {
override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
// Authentication Error
}
override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
lifecycleScope.launch(Main) {
navigateToSavedLoginsFragment()
}
}
override fun onAuthenticationFailed() {
// Authenticated Failed
}
}
val executor = Executors.newSingleThreadExecutor()
val biometricPrompt = BiometricPrompt(this, executor, biometricPromptCallback)
val promptInfo = BiometricPrompt.PromptInfo.Builder()
.setTitle(getString(R.string.logins_biometric_prompt_message))
.setDeviceCredentialAllowed(true)
.build()
biometricPrompt.authenticate(promptInfo)
}
private fun verifyPinOrShowSetupWarning() {
val manager = activity?.getSystemService(KEYGUARD_SERVICE) as KeyguardManager
if (manager.isKeyguardSecure) {
showPinVerification()
} else {
if (context?.settings()?.shouldShowSecurityPinWarning == true) {
showPinDialogWarning()
} else {
navigateToSavedLoginsFragment()
}
}
}
private fun showPinDialogWarning() {
context?.let {
AlertDialog.Builder(it).apply {
setTitle(getString(R.string.logins_warning_dialog_title))
setMessage(
getString(R.string.logins_warning_dialog_message)
)
setNegativeButton(getString(R.string.logins_warning_dialog_later)) { _: DialogInterface, _ ->
navigateToSavedLoginsFragment()
}
setPositiveButton(getString(R.string.logins_warning_dialog_set_up_now)) { it: DialogInterface, _ ->
it.dismiss()
val intent = Intent(
android.provider.Settings.ACTION_SECURITY_SETTINGS
)
startActivity(intent)
}
create()
}.show()
it.settings().incrementShowLoginsSecureWarningCount()
}
}
private fun showPinVerification() {
val manager = activity?.getSystemService(KEYGUARD_SERVICE) as KeyguardManager
val intent = manager.createConfirmDeviceCredentialIntent(
getString(R.string.logins_biometric_prompt_message_pin),
getString(R.string.logins_biometric_prompt_message)
)
startActivityForResult(intent, PIN_REQUEST)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == PIN_REQUEST && resultCode == RESULT_OK) {
navigateToSavedLoginsFragment()
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
private fun navigateToSavedLoginsFragment() {
val directions = LoginsFragmentDirections.actionLoginsFragmentToSavedLoginsFragment() val directions = LoginsFragmentDirections.actionLoginsFragmentToSavedLoginsFragment()
findNavController().navigate(directions) findNavController().navigate(directions)
} }
@ -111,4 +242,8 @@ class LoginsFragment : PreferenceFragmentCompat(), AccountObserver {
val directions = LoginsFragmentDirections.actionLoginsFragmentToTurnOnSyncFragment() val directions = LoginsFragmentDirections.actionLoginsFragmentToTurnOnSyncFragment()
findNavController().navigate(directions) findNavController().navigate(directions)
} }
companion object {
const val PIN_REQUEST = 303
}
} }

View File

@ -4,10 +4,16 @@
package org.mozilla.fenix.settings.account package org.mozilla.fenix.settings.account
import android.app.KeyguardManager
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.provider.Settings
import android.text.InputFilter import android.text.InputFilter
import android.text.format.DateUtils import android.text.format.DateUtils
import android.view.View import android.view.View
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
@ -36,6 +42,7 @@ import org.mozilla.fenix.components.metrics.Event
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getPreferenceKey import org.mozilla.fenix.ext.getPreferenceKey
import org.mozilla.fenix.ext.requireComponents import org.mozilla.fenix.ext.requireComponents
import org.mozilla.fenix.ext.settings
@SuppressWarnings("TooManyFunctions", "LargeClass") @SuppressWarnings("TooManyFunctions", "LargeClass")
class AccountSettingsFragment : PreferenceFragmentCompat() { class AccountSettingsFragment : PreferenceFragmentCompat() {
@ -180,14 +187,27 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
val loginsNameKey = getPreferenceKey(R.string.pref_key_sync_logins) val loginsNameKey = getPreferenceKey(R.string.pref_key_sync_logins)
findPreference<CheckBoxPreference>(loginsNameKey)?.apply { findPreference<CheckBoxPreference>(loginsNameKey)?.apply {
setOnPreferenceChangeListener { _, newValue -> setOnPreferenceChangeListener { _, newValue ->
SyncEnginesStorage(context).setStatus(SyncEngine.Passwords, newValue as Boolean) val manager =
@Suppress("DeferredResultUnused") activity?.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange) if (manager.isKeyguardSecure ||
newValue == false ||
!context.settings().shouldShowSecurityPinWarningSync
) {
SyncEnginesStorage(context).setStatus(SyncEngine.Passwords, newValue as Boolean)
@Suppress("DeferredResultUnused")
context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange)
} else {
showPinDialogWarning(newValue as Boolean)
}
true true
} }
} }
deviceConstellation?.registerDeviceObserver(deviceConstellationObserver, owner = this, autoPause = true) deviceConstellation?.registerDeviceObserver(
deviceConstellationObserver,
owner = this,
autoPause = true
)
// NB: ObserverRegistry will take care of cleaning up internal references to 'observer' and // NB: ObserverRegistry will take care of cleaning up internal references to 'observer' and
// 'owner' when appropriate. // 'owner' when appropriate.
@ -196,6 +216,33 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
) )
} }
private fun showPinDialogWarning(newValue: Boolean) {
context?.let {
AlertDialog.Builder(it).apply {
setTitle(getString(R.string.logins_warning_dialog_title))
setMessage(
getString(R.string.logins_warning_dialog_message)
)
setNegativeButton(getString(R.string.logins_warning_dialog_later)) { _: DialogInterface, _ ->
SyncEnginesStorage(context).setStatus(SyncEngine.Passwords, newValue)
@Suppress("DeferredResultUnused")
context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange)
}
setPositiveButton(getString(R.string.logins_warning_dialog_set_up_now)) { it: DialogInterface, _ ->
it.dismiss()
val intent = Intent(
Settings.ACTION_SECURITY_SETTINGS
)
startActivity(intent)
}
create()
}.show()
it.settings().incrementShowLoginsSecureWarningSyncCount()
}
}
private fun updateSyncEngineStates() { private fun updateSyncEngineStates() {
val syncEnginesStatus = SyncEnginesStorage(context!!).getStatus() val syncEnginesStatus = SyncEnginesStorage(context!!).getStatus()
val bookmarksNameKey = getPreferenceKey(R.string.pref_key_sync_bookmarks) val bookmarksNameKey = getPreferenceKey(R.string.pref_key_sync_bookmarks)

View File

@ -37,6 +37,8 @@ class Settings private constructor(
private val isCrashReportEnabledInBuild: Boolean private val isCrashReportEnabledInBuild: Boolean
) : PreferencesHolder { ) : PreferencesHolder {
companion object { companion object {
const val showLoginsSecureWarningSyncMaxCount = 1
const val showLoginsSecureWarningMaxCount = 1
const val autoBounceMaximumCount = 2 const val autoBounceMaximumCount = 2
const val trackingProtectionOnboardingMaximumCount = 2 const val trackingProtectionOnboardingMaximumCount = 2
const val FENIX_PREFERENCES = "fenix_preferences" const val FENIX_PREFERENCES = "fenix_preferences"
@ -133,6 +135,12 @@ class Settings private constructor(
val shouldAutoBounceQuickActionSheet: Boolean val shouldAutoBounceQuickActionSheet: Boolean
get() = autoBounceQuickActionSheetCount < autoBounceMaximumCount get() = autoBounceQuickActionSheetCount < autoBounceMaximumCount
val shouldShowSecurityPinWarningSync: Boolean
get() = loginsSecureWarningSyncCount < showLoginsSecureWarningSyncMaxCount
val shouldShowSecurityPinWarning: Boolean
get() = loginsSecureWarningCount < showLoginsSecureWarningMaxCount
var shouldUseLightTheme by booleanPreference( var shouldUseLightTheme by booleanPreference(
appContext.getPreferenceKey(R.string.pref_key_light_theme), appContext.getPreferenceKey(R.string.pref_key_light_theme),
default = false default = false
@ -249,6 +257,32 @@ class Settings private constructor(
default = 0 default = 0
) )
@VisibleForTesting(otherwise = PRIVATE)
internal val loginsSecureWarningSyncCount by intPreference(
appContext.getPreferenceKey(R.string.pref_key_logins_secure_warning_sync),
default = 0
)
@VisibleForTesting(otherwise = PRIVATE)
internal val loginsSecureWarningCount by intPreference(
appContext.getPreferenceKey(R.string.pref_key_logins_secure_warning),
default = 0
)
fun incrementShowLoginsSecureWarningCount() {
preferences.edit().putInt(
appContext.getPreferenceKey(R.string.pref_key_logins_secure_warning),
loginsSecureWarningCount + 1
).apply()
}
fun incrementShowLoginsSecureWarningSyncCount() {
preferences.edit().putInt(
appContext.getPreferenceKey(R.string.pref_key_logins_secure_warning_sync),
loginsSecureWarningSyncCount + 1
).apply()
}
fun incrementAutomaticBounceQuickActionSheetCount() { fun incrementAutomaticBounceQuickActionSheetCount() {
preferences.edit().putInt( preferences.edit().putInt(
appContext.getPreferenceKey(R.string.pref_key_bounce_quick_action), appContext.getPreferenceKey(R.string.pref_key_bounce_quick_action),

View File

@ -104,6 +104,8 @@
<!-- Logins Settings --> <!-- Logins Settings -->
<string name="pref_key_saved_logins" translatable="false">pref_key_saved_logins</string> <string name="pref_key_saved_logins" translatable="false">pref_key_saved_logins</string>
<string name="pref_key_password_sync_logins" translatable="false">pref_key_password_sync_logins</string> <string name="pref_key_password_sync_logins" translatable="false">pref_key_password_sync_logins</string>
<string name="pref_key_logins_secure_warning_sync" translatable="false">pref_key_logins_secure_warning_sync</string>
<string name="pref_key_logins_secure_warning" translatable="false">pref_key_logins_secure_warning</string>
<!-- Privacy Settings --> <!-- Privacy Settings -->
<string name="pref_key_open_links_in_a_private_tab" translatable="false">pref_key_open_links_in_a_private_tab</string> <string name="pref_key_open_links_in_a_private_tab" translatable="false">pref_key_open_links_in_a_private_tab</string>

View File

@ -1009,4 +1009,16 @@
<string name="saved_login_reveal_password">Show password</string> <string name="saved_login_reveal_password">Show password</string>
<!-- Content Description (for screenreaders etc) read for the button to hide a password in logins --> <!-- Content Description (for screenreaders etc) read for the button to hide a password in logins -->
<string name="saved_login_hide_password">Hide password</string> <string name="saved_login_hide_password">Hide password</string>
<!-- Message displayed in biometric prompt displayed for authentication before allowing users to view their logins -->
<string name="logins_biometric_prompt_message">Unlock to view your saved logins</string>
<!-- Title of warning dialog if users have no device authentication set up -->
<string name="logins_warning_dialog_title">Secure your logins and passwords</string>
<!-- Message of warning dialog if users have no device authentication set up -->
<string name="logins_warning_dialog_message">Set up a device lock pattern, PIN, or password to protect your saved logins and passwords from being accessed if someone else has your device.</string>
<!-- Negative button to ignore warning dialog if users have no device authentication set up -->
<string name="logins_warning_dialog_later">Later</string>
<!-- Positive button to send users to set up a pin of warning dialog if users have no device authentication set up -->
<string name="logins_warning_dialog_set_up_now">Set up now</string>
<!-- Title of PIN verification dialog to direct users to re-enter their device credentials to access their logins -->
<string name="logins_biometric_prompt_message_pin">Unlock your device</string>
</resources> </resources>

View File

@ -173,6 +173,58 @@ class SettingsTest {
assertFalse(settings.shouldAutoBounceQuickActionSheet) assertFalse(settings.shouldAutoBounceQuickActionSheet)
} }
@Test
fun showLoginsDialogWarningSync() {
// When just created
// Then
assertEquals(0, settings.loginsSecureWarningSyncCount)
// When
settings.incrementShowLoginsSecureWarningSyncCount()
// Then
assertEquals(1, settings.loginsSecureWarningSyncCount)
}
@Test
fun shouldShowLoginsDialogWarningSync() {
// When just created
// Then
assertTrue(settings.shouldShowSecurityPinWarningSync)
// When
settings.incrementShowLoginsSecureWarningSyncCount()
// Then
assertFalse(settings.shouldShowSecurityPinWarningSync)
}
@Test
fun showLoginsDialogWarning() {
// When just created
// Then
assertEquals(0, settings.loginsSecureWarningCount)
// When
settings.incrementShowLoginsSecureWarningCount()
// Then
assertEquals(1, settings.loginsSecureWarningCount)
}
@Test
fun shouldShowLoginsDialogWarning() {
// When just created
// Then
assertTrue(settings.shouldShowSecurityPinWarning)
// When
settings.incrementShowLoginsSecureWarningCount()
// Then
assertFalse(settings.shouldShowSecurityPinWarning)
}
@Test @Test
fun shouldUseLightTheme() { fun shouldUseLightTheme() {
// When just created // When just created
@ -298,7 +350,10 @@ class SettingsTest {
fun sitePermissionsPhoneFeatureCameraAction() { fun sitePermissionsPhoneFeatureCameraAction() {
// When just created // When just created
// Then // Then
assertEquals(ASK_TO_ALLOW, settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.CAMERA)) assertEquals(
ASK_TO_ALLOW,
settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.CAMERA)
)
// When // When
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.CAMERA, BLOCKED) settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.CAMERA, BLOCKED)
@ -311,33 +366,48 @@ class SettingsTest {
fun sitePermissionsPhoneFeatureMicrophoneAction() { fun sitePermissionsPhoneFeatureMicrophoneAction() {
// When just created // When just created
// Then // Then
assertEquals(ASK_TO_ALLOW, settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.MICROPHONE)) assertEquals(
ASK_TO_ALLOW,
settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.MICROPHONE)
)
// When // When
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.MICROPHONE, BLOCKED) settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.MICROPHONE, BLOCKED)
// Then // Then
assertEquals(BLOCKED, settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.MICROPHONE)) assertEquals(
BLOCKED,
settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.MICROPHONE)
)
} }
@Test @Test
fun sitePermissionsPhoneFeatureNotificationAction() { fun sitePermissionsPhoneFeatureNotificationAction() {
// When just created // When just created
// Then // Then
assertEquals(ASK_TO_ALLOW, settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.NOTIFICATION)) assertEquals(
ASK_TO_ALLOW,
settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.NOTIFICATION)
)
// When // When
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.NOTIFICATION, BLOCKED) settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.NOTIFICATION, BLOCKED)
// Then // Then
assertEquals(BLOCKED, settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.NOTIFICATION)) assertEquals(
BLOCKED,
settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.NOTIFICATION)
)
} }
@Test @Test
fun sitePermissionsPhoneFeatureLocation() { fun sitePermissionsPhoneFeatureLocation() {
// When just created // When just created
// Then // Then
assertEquals(ASK_TO_ALLOW, settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.LOCATION)) assertEquals(
ASK_TO_ALLOW,
settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.LOCATION)
)
// When // When
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.LOCATION, BLOCKED) settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.LOCATION, BLOCKED)

View File

@ -18,6 +18,7 @@ object Versions {
const val osslicenses_library = "17.0.0" const val osslicenses_library = "17.0.0"
const val androidx_appcompat = "1.1.0" const val androidx_appcompat = "1.1.0"
const val androidx_biometric = "1.0.0-rc02"
const val androidx_coordinator_layout = "1.1.0-beta01" const val androidx_coordinator_layout = "1.1.0-beta01"
const val androidx_constraint_layout = "2.0.0-beta2" const val androidx_constraint_layout = "2.0.0-beta2"
const val androidx_preference = "1.1.0" const val androidx_preference = "1.1.0"
@ -166,6 +167,7 @@ object Deps {
const val leanplum = "com.leanplum:leanplum-core:${Versions.leanplum}" const val leanplum = "com.leanplum:leanplum-core:${Versions.leanplum}"
const val androidx_annotation = "androidx.annotation:annotation:${Versions.androidx_annotation}" const val androidx_annotation = "androidx.annotation:annotation:${Versions.androidx_annotation}"
const val androidx_biometric = "androidx.biometric:biometric:${Versions.androidx_biometric}"
const val androidx_fragment = "androidx.fragment:fragment-ktx:${Versions.androidx_fragment}" const val androidx_fragment = "androidx.fragment:fragment-ktx:${Versions.androidx_fragment}"
const val androidx_appcompat = "androidx.appcompat:appcompat:${Versions.androidx_appcompat}" const val androidx_appcompat = "androidx.appcompat:appcompat:${Versions.androidx_appcompat}"
const val androidx_coordinatorlayout = "androidx.coordinatorlayout:coordinatorlayout:${Versions.androidx_coordinator_layout}" const val androidx_coordinatorlayout = "androidx.coordinatorlayout:coordinatorlayout:${Versions.androidx_coordinator_layout}"