For #5547 - Add top level auth when accessing passwords
parent
218763f9be
commit
3e2b88cc91
|
@ -405,6 +405,7 @@ dependencies {
|
|||
debugImplementation Deps.leakcanary
|
||||
|
||||
implementation Deps.androidx_legacy
|
||||
implementation Deps.androidx_biometric
|
||||
implementation Deps.androidx_paging
|
||||
implementation Deps.androidx_preference
|
||||
implementation Deps.androidx_fragment
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<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 -->
|
||||
<uses-permission-sdk-23 android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
|
|
|
@ -9,6 +9,7 @@ import android.text.InputType
|
|||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.google.android.material.snackbar.Snackbar
|
||||
import kotlinx.android.synthetic.main.fragment_saved_login_site_info.*
|
||||
import org.mozilla.fenix.R
|
||||
|
@ -24,6 +25,12 @@ class SavedLoginSiteInfoFragment : Fragment(R.layout.fragment_saved_login_site_i
|
|||
).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?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
|
|
|
@ -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) {
|
||||
val directions =
|
||||
SavedLoginsFragmentDirections.actionSavedLoginsFragmentToSavedLoginSiteInfoFragment(item)
|
||||
|
|
|
@ -4,11 +4,25 @@
|
|||
|
||||
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 androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.biometric.BiometricManager
|
||||
import androidx.biometric.BiometricPrompt
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import kotlinx.coroutines.Dispatchers.Main
|
||||
import kotlinx.coroutines.launch
|
||||
import mozilla.components.concept.sync.AccountObserver
|
||||
import mozilla.components.concept.sync.AuthType
|
||||
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.ext.getPreferenceKey
|
||||
import org.mozilla.fenix.ext.requireComponents
|
||||
import org.mozilla.fenix.ext.settings
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
class LoginsFragment : PreferenceFragmentCompat(), AccountObserver {
|
||||
|
@ -31,7 +47,11 @@ class LoginsFragment : PreferenceFragmentCompat(), AccountObserver {
|
|||
|
||||
val savedLoginsKey = getPreferenceKey(R.string.pref_key_saved_logins)
|
||||
findPreference<Preference>(savedLoginsKey)?.setOnPreferenceClickListener {
|
||||
navigateToLoginsSettingsFragment()
|
||||
if (Build.VERSION.SDK_INT >= M && isHardwareAvailable && hasBiometricEnrolled) {
|
||||
showBiometricPrompt()
|
||||
} else {
|
||||
verifyPinOrShowSetupWarning()
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
|
@ -54,6 +74,31 @@ class LoginsFragment : PreferenceFragmentCompat(), AccountObserver {
|
|||
|
||||
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() {
|
||||
val syncLogins = getPreferenceKey(R.string.pref_key_password_sync_logins)
|
||||
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()
|
||||
findNavController().navigate(directions)
|
||||
}
|
||||
|
@ -111,4 +242,8 @@ class LoginsFragment : PreferenceFragmentCompat(), AccountObserver {
|
|||
val directions = LoginsFragmentDirections.actionLoginsFragmentToTurnOnSyncFragment()
|
||||
findNavController().navigate(directions)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val PIN_REQUEST = 303
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,10 +4,16 @@
|
|||
|
||||
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.provider.Settings
|
||||
import android.text.InputFilter
|
||||
import android.text.format.DateUtils
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
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.getPreferenceKey
|
||||
import org.mozilla.fenix.ext.requireComponents
|
||||
import org.mozilla.fenix.ext.settings
|
||||
|
||||
@SuppressWarnings("TooManyFunctions", "LargeClass")
|
||||
class AccountSettingsFragment : PreferenceFragmentCompat() {
|
||||
|
@ -180,14 +187,27 @@ class AccountSettingsFragment : PreferenceFragmentCompat() {
|
|||
val loginsNameKey = getPreferenceKey(R.string.pref_key_sync_logins)
|
||||
findPreference<CheckBoxPreference>(loginsNameKey)?.apply {
|
||||
setOnPreferenceChangeListener { _, newValue ->
|
||||
SyncEnginesStorage(context).setStatus(SyncEngine.Passwords, newValue as Boolean)
|
||||
@Suppress("DeferredResultUnused")
|
||||
context.components.backgroundServices.accountManager.syncNowAsync(SyncReason.EngineChange)
|
||||
val manager =
|
||||
activity?.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
// '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() {
|
||||
val syncEnginesStatus = SyncEnginesStorage(context!!).getStatus()
|
||||
val bookmarksNameKey = getPreferenceKey(R.string.pref_key_sync_bookmarks)
|
||||
|
|
|
@ -37,6 +37,8 @@ class Settings private constructor(
|
|||
private val isCrashReportEnabledInBuild: Boolean
|
||||
) : PreferencesHolder {
|
||||
companion object {
|
||||
const val showLoginsSecureWarningSyncMaxCount = 1
|
||||
const val showLoginsSecureWarningMaxCount = 1
|
||||
const val autoBounceMaximumCount = 2
|
||||
const val trackingProtectionOnboardingMaximumCount = 2
|
||||
const val FENIX_PREFERENCES = "fenix_preferences"
|
||||
|
@ -133,6 +135,12 @@ class Settings private constructor(
|
|||
val shouldAutoBounceQuickActionSheet: Boolean
|
||||
get() = autoBounceQuickActionSheetCount < autoBounceMaximumCount
|
||||
|
||||
val shouldShowSecurityPinWarningSync: Boolean
|
||||
get() = loginsSecureWarningSyncCount < showLoginsSecureWarningSyncMaxCount
|
||||
|
||||
val shouldShowSecurityPinWarning: Boolean
|
||||
get() = loginsSecureWarningCount < showLoginsSecureWarningMaxCount
|
||||
|
||||
var shouldUseLightTheme by booleanPreference(
|
||||
appContext.getPreferenceKey(R.string.pref_key_light_theme),
|
||||
default = false
|
||||
|
@ -249,6 +257,32 @@ class Settings private constructor(
|
|||
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() {
|
||||
preferences.edit().putInt(
|
||||
appContext.getPreferenceKey(R.string.pref_key_bounce_quick_action),
|
||||
|
|
|
@ -104,6 +104,8 @@
|
|||
<!-- Logins Settings -->
|
||||
<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_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 -->
|
||||
<string name="pref_key_open_links_in_a_private_tab" translatable="false">pref_key_open_links_in_a_private_tab</string>
|
||||
|
|
|
@ -1009,4 +1009,16 @@
|
|||
<string name="saved_login_reveal_password">Show password</string>
|
||||
<!-- Content Description (for screenreaders etc) read for the button to hide a password in logins -->
|
||||
<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>
|
||||
|
|
|
@ -173,6 +173,58 @@ class SettingsTest {
|
|||
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
|
||||
fun shouldUseLightTheme() {
|
||||
// When just created
|
||||
|
@ -298,7 +350,10 @@ class SettingsTest {
|
|||
fun sitePermissionsPhoneFeatureCameraAction() {
|
||||
// When just created
|
||||
// Then
|
||||
assertEquals(ASK_TO_ALLOW, settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.CAMERA))
|
||||
assertEquals(
|
||||
ASK_TO_ALLOW,
|
||||
settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.CAMERA)
|
||||
)
|
||||
|
||||
// When
|
||||
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.CAMERA, BLOCKED)
|
||||
|
@ -311,33 +366,48 @@ class SettingsTest {
|
|||
fun sitePermissionsPhoneFeatureMicrophoneAction() {
|
||||
// When just created
|
||||
// Then
|
||||
assertEquals(ASK_TO_ALLOW, settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.MICROPHONE))
|
||||
assertEquals(
|
||||
ASK_TO_ALLOW,
|
||||
settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.MICROPHONE)
|
||||
)
|
||||
|
||||
// When
|
||||
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.MICROPHONE, BLOCKED)
|
||||
|
||||
// Then
|
||||
assertEquals(BLOCKED, settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.MICROPHONE))
|
||||
assertEquals(
|
||||
BLOCKED,
|
||||
settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.MICROPHONE)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sitePermissionsPhoneFeatureNotificationAction() {
|
||||
// When just created
|
||||
// Then
|
||||
assertEquals(ASK_TO_ALLOW, settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.NOTIFICATION))
|
||||
assertEquals(
|
||||
ASK_TO_ALLOW,
|
||||
settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.NOTIFICATION)
|
||||
)
|
||||
|
||||
// When
|
||||
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.NOTIFICATION, BLOCKED)
|
||||
|
||||
// Then
|
||||
assertEquals(BLOCKED, settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.NOTIFICATION))
|
||||
assertEquals(
|
||||
BLOCKED,
|
||||
settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.NOTIFICATION)
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun sitePermissionsPhoneFeatureLocation() {
|
||||
// When just created
|
||||
// Then
|
||||
assertEquals(ASK_TO_ALLOW, settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.LOCATION))
|
||||
assertEquals(
|
||||
ASK_TO_ALLOW,
|
||||
settings.getSitePermissionsPhoneFeatureAction(PhoneFeature.LOCATION)
|
||||
)
|
||||
|
||||
// When
|
||||
settings.setSitePermissionsPhoneFeatureAction(PhoneFeature.LOCATION, BLOCKED)
|
||||
|
|
|
@ -18,6 +18,7 @@ object Versions {
|
|||
const val osslicenses_library = "17.0.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_constraint_layout = "2.0.0-beta2"
|
||||
const val androidx_preference = "1.1.0"
|
||||
|
@ -166,6 +167,7 @@ object Deps {
|
|||
const val leanplum = "com.leanplum:leanplum-core:${Versions.leanplum}"
|
||||
|
||||
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_appcompat = "androidx.appcompat:appcompat:${Versions.androidx_appcompat}"
|
||||
const val androidx_coordinatorlayout = "androidx.coordinatorlayout:coordinatorlayout:${Versions.androidx_coordinator_layout}"
|
||||
|
|
Loading…
Reference in New Issue