From a0980bf0518b299aa065caf431281de705810b71 Mon Sep 17 00:00:00 2001 From: amddg44 Date: Sun, 8 Oct 2023 14:17:23 +0200 Subject: [PATCH] Autofill onByDefault rollout for new installs (#2056) Task/Issue URL: https://app.asana.com/0/0/1205296228305939/f Tech Design URL: CC: Description: Adds support for rolling out Autofill as on by default for new installs based on the incremental feature flag onByDefault --- Core/FeatureFlag.swift | 3 + DuckDuckGo.xcodeproj/project.pbxproj | 4 +- .../xcshareddata/swiftpm/Package.resolved | 10 +-- DuckDuckGo/AppDelegate.swift | 6 ++ DuckDuckGo/AppSettings.swift | 2 + DuckDuckGo/AppUserDefaults.swift | 34 +++++++-- DuckDuckGoTests/AppSettingsMock.swift | 4 ++ DuckDuckGoTests/AppUserDefaultsTests.swift | 72 ++++++++++++++++--- 8 files changed, 114 insertions(+), 21 deletions(-) diff --git a/Core/FeatureFlag.swift b/Core/FeatureFlag.swift index 69269aa99c..c31da129d1 100644 --- a/Core/FeatureFlag.swift +++ b/Core/FeatureFlag.swift @@ -28,6 +28,7 @@ public enum FeatureFlag: String { case autofillInlineIconCredentials case autofillAccessCredentialManagement case autofillPasswordGeneration + case autofillOnByDefault case incontextSignup case appTrackingProtection case networkProtection @@ -48,6 +49,8 @@ extension FeatureFlag: FeatureFlagSourceProviding { return .remoteReleasable(.subfeature(AutofillSubfeature.accessCredentialManagement)) case .autofillPasswordGeneration: return .remoteReleasable(.subfeature(AutofillSubfeature.autofillPasswordGeneration)) + case .autofillOnByDefault: + return .remoteReleasable(.subfeature(AutofillSubfeature.onByDefault)) case .incontextSignup: return .remoteReleasable(.feature(.incontextSignup)) } diff --git a/DuckDuckGo.xcodeproj/project.pbxproj b/DuckDuckGo.xcodeproj/project.pbxproj index bba49be20b..e401c659f6 100644 --- a/DuckDuckGo.xcodeproj/project.pbxproj +++ b/DuckDuckGo.xcodeproj/project.pbxproj @@ -8955,8 +8955,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/DuckDuckGo/BrowserServicesKit"; requirement = { - kind = exactVersion; - version = 80.4.1; + branch = "anya/autofill-on-default-flag"; + kind = branch; }; }; C14882EB27F211A000D59F0C /* XCRemoteSwiftPackageReference "SwiftSoup" */ = { diff --git a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f903a9e2ac..5ec51c5b8d 100644 --- a/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/DuckDuckGo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -14,9 +14,9 @@ "package": "BrowserServicesKit", "repositoryURL": "https://github.com/DuckDuckGo/BrowserServicesKit", "state": { - "branch": null, - "revision": "9dea0583dc6269971fb4728bd3efa1ed53f88306", - "version": "80.4.1" + "branch": "anya/autofill-on-default-flag", + "revision": "885f327b2fc1793eda26308fa3cb81995ba1b403", + "version": null } }, { @@ -33,8 +33,8 @@ "repositoryURL": "https://github.com/duckduckgo/content-scope-scripts", "state": { "branch": null, - "revision": "8def15fe8a4c2fb76730f640507e9fd1d6c1f8a7", - "version": "4.32.0" + "revision": "74b6142c016be354144f28551de41b50c4864b1f", + "version": "4.37.0" } }, { diff --git a/DuckDuckGo/AppDelegate.swift b/DuckDuckGo/AppDelegate.swift index 86ca911d04..b1f637d357 100644 --- a/DuckDuckGo/AppDelegate.swift +++ b/DuckDuckGo/AppDelegate.swift @@ -245,6 +245,12 @@ class AppDelegate: UIResponder, UIApplicationDelegate { ThemeManager.shared.updateUserInterfaceStyle(window: window) appIsLaunching = true + + // Temporary logic for rollout of Autofill as on by default for new installs only + if AppDependencyProvider.shared.appSettings.autofillIsNewInstallForOnByDefault == nil { + AppDependencyProvider.shared.appSettings.setAutofillIsNewInstallForOnByDefault() + } + return true } diff --git a/DuckDuckGo/AppSettings.swift b/DuckDuckGo/AppSettings.swift index 6e9a72c829..d79c06e746 100644 --- a/DuckDuckGo/AppSettings.swift +++ b/DuckDuckGo/AppSettings.swift @@ -37,6 +37,8 @@ protocol AppSettings: AnyObject { var autofillCredentialsEnabled: Bool { get set } var autofillCredentialsSavePromptShowAtLeastOnce: Bool { get set } var autofillCredentialsHasBeenEnabledAutomaticallyIfNecessary: Bool { get set } + var autofillIsNewInstallForOnByDefault: Bool? { get set } + func setAutofillIsNewInstallForOnByDefault() var voiceSearchEnabled: Bool { get set } diff --git a/DuckDuckGo/AppUserDefaults.swift b/DuckDuckGo/AppUserDefaults.swift index cdc13cbb36..0a7d98d880 100644 --- a/DuckDuckGo/AppUserDefaults.swift +++ b/DuckDuckGo/AppUserDefaults.swift @@ -60,6 +60,7 @@ public class AppUserDefaults: AppSettings { static let currentFireButtonAnimationKey = "com.duckduckgo.app.currentFireButtonAnimationKey" static let autofillCredentialsEnabled = "com.duckduckgo.ios.autofillCredentialsEnabled" + static let autofillIsNewInstallForOnByDefault = "com.duckduckgo.ios.autofillIsNewInstallForOnByDefault" } private struct DebugKeys { @@ -70,6 +71,8 @@ public class AppUserDefaults: AppSettings { return UserDefaults(suiteName: groupName) } + lazy var featureFlagger = AppDependencyProvider.shared.featureFlagger + init(groupName: String = "group.com.duckduckgo.app") { self.groupName = groupName } @@ -182,16 +185,22 @@ public class AppUserDefaults: AppSettings { return } if !autofillCredentialsSavePromptShowAtLeastOnce { - autofillCredentialsHasBeenEnabledAutomaticallyIfNecessary = true - autofillCredentialsEnabled = true + if let isNewInstall = autofillIsNewInstallForOnByDefault, + isNewInstall, + featureFlagger.isFeatureOn(.autofillOnByDefault) { + autofillCredentialsHasBeenEnabledAutomaticallyIfNecessary = true + autofillCredentialsEnabled = true + } } } var autofillCredentialsEnabled: Bool { get { - // In future, we'll use setAutofillCredentialsEnabledAutomaticallyIfNecessary() here to automatically turn on autofill for people - // That haven't seen the save prompt before. - // For now, whilst internal testing is still happening, it's still set to default to be enabled + // setAutofillCredentialsEnabledAutomaticallyIfNecessary() used here to automatically turn on autofill for people if: + // 1. They haven't seen the save prompt before + // 2. They are a new install + // 3. The feature flag is enabled + setAutofillCredentialsEnabledAutomaticallyIfNecessary() return userDefaults?.object(forKey: Keys.autofillCredentialsEnabled) as? Bool ?? false } @@ -205,7 +214,20 @@ public class AppUserDefaults: AppSettings { @UserDefaultsWrapper(key: .autofillCredentialsHasBeenEnabledAutomaticallyIfNecessary, defaultValue: false) var autofillCredentialsHasBeenEnabledAutomaticallyIfNecessary: Bool - + + var autofillIsNewInstallForOnByDefault: Bool? { + get { + return userDefaults?.object(forKey: Keys.autofillIsNewInstallForOnByDefault) as? Bool + } + set { + userDefaults?.set(newValue, forKey: Keys.autofillIsNewInstallForOnByDefault) + } + } + + func setAutofillIsNewInstallForOnByDefault() { + autofillIsNewInstallForOnByDefault = StatisticsUserDefaults().installDate == nil + } + @UserDefaultsWrapper(key: .voiceSearchEnabled, defaultValue: false) var voiceSearchEnabled: Bool diff --git a/DuckDuckGoTests/AppSettingsMock.swift b/DuckDuckGoTests/AppSettingsMock.swift index 53f099d6b8..a31dd2a1e1 100644 --- a/DuckDuckGoTests/AppSettingsMock.swift +++ b/DuckDuckGoTests/AppSettingsMock.swift @@ -27,6 +27,10 @@ class AppSettingsMock: AppSettings { var autofillCredentialsHasBeenEnabledAutomaticallyIfNecessary: Bool = false + var autofillIsNewInstallForOnByDefault: Bool? + + func setAutofillIsNewInstallForOnByDefault() { } + var autocomplete: Bool = true var currentThemeName: DuckDuckGo.ThemeName = .systemDefault diff --git a/DuckDuckGoTests/AppUserDefaultsTests.swift b/DuckDuckGoTests/AppUserDefaultsTests.swift index d1eb24c056..89d6e4703f 100644 --- a/DuckDuckGoTests/AppUserDefaultsTests.swift +++ b/DuckDuckGoTests/AppUserDefaultsTests.swift @@ -19,14 +19,22 @@ import XCTest @testable import DuckDuckGo +import BrowserServicesKit class AppUserDefaultsTests: XCTestCase { let testGroupName = "test" + var internalUserDeciderStore: MockInternalUserStoring! override func setUp() { super.setUp() UserDefaults(suiteName: testGroupName)?.removePersistentDomain(forName: testGroupName) + internalUserDeciderStore = MockInternalUserStoring() + } + + override func tearDown() { + internalUserDeciderStore = nil + super.tearDown() } func testWhenLinkPreviewsIsSetThenItIsPersisted() { @@ -93,17 +101,46 @@ class AppUserDefaultsTests: XCTestCase { let appUserDefaults = AppUserDefaults(groupName: testGroupName) XCTAssertFalse(appUserDefaults.autofillCredentialsEnabled) } - - /* - These tests aren't required until we make autofill default to off, and then enable turning it on automatically - func testWhenAutofillCredentialsIsDisabledAndHasNotBeenTurnedOnAutomaticallyBeforeThenAutofillCredentialsEnabled() { + + func testWhenAutofillCredentialsIsDisabledAndHasNotBeenTurnedOnAutomaticallyBeforeWhenSavePromptShownThenDefaultAutofillStateIsFalse() { let appUserDefaults = AppUserDefaults(groupName: testGroupName) - appUserDefaults.autofillCredentialsEnabled = false + appUserDefaults.autofillCredentialsHasBeenEnabledAutomaticallyIfNecessary = false + appUserDefaults.autofillCredentialsSavePromptShowAtLeastOnce = true + + XCTAssertFalse(appUserDefaults.autofillCredentialsEnabled) + } + + func testWhenAutofillCredentialsIsDisabledAndHasNotBeenTurnedOnAutomaticallyBeforeAndPromptHasNotBeenSeenAndIsNotNewInstallThenDefaultAutofillStateIsFalse() { + let appUserDefaults = AppUserDefaults(groupName: testGroupName) + appUserDefaults.autofillCredentialsHasBeenEnabledAutomaticallyIfNecessary = false appUserDefaults.autofillCredentialsSavePromptShowAtLeastOnce = false + appUserDefaults.autofillIsNewInstallForOnByDefault = false + + XCTAssertFalse(appUserDefaults.autofillCredentialsEnabled) + } + + func testWhenAutofillCredentialsIsDisabledAndHasNotBeenTurnedOnAutomaticallyBeforeAndPromptHasNotBeenSeenAndIsNewInstallAndFeatureFlagDisabledThenDefaultAutofillStateIsFalse() { + let appUserDefaults = AppUserDefaults(groupName: testGroupName) appUserDefaults.autofillCredentialsHasBeenEnabledAutomaticallyIfNecessary = false - XCTAssertEqual(appUserDefaults.autofillCredentialsEnabled, true) + appUserDefaults.autofillCredentialsSavePromptShowAtLeastOnce = false + appUserDefaults.autofillIsNewInstallForOnByDefault = true + let featureFlagger = createFeatureFlagger(withSubfeatureEnabled: false) + appUserDefaults.featureFlagger = featureFlagger + + XCTAssertFalse(appUserDefaults.autofillCredentialsEnabled) } - + + func testWhenAutofillCredentialsIsDisabledAndHasNotBeenTurnedOnAutomaticallyBeforeAndPromptHasNotBeenSeenAndIsNewInstallAndFeatureFlagEnabledThenDefaultAutofillStateIsTrue() { + let appUserDefaults = AppUserDefaults(groupName: testGroupName) + appUserDefaults.autofillCredentialsHasBeenEnabledAutomaticallyIfNecessary = false + appUserDefaults.autofillCredentialsSavePromptShowAtLeastOnce = false + appUserDefaults.autofillIsNewInstallForOnByDefault = true + let featureFlagger = createFeatureFlagger(withSubfeatureEnabled: true) + appUserDefaults.featureFlagger = featureFlagger + + XCTAssertTrue(appUserDefaults.autofillCredentialsEnabled) + } + func testWhenAutofillCredentialsIsDisabledAndHasNotBeenTurnedOnAutomaticallyBeforeAndPromptHasBeenSeenThenAutofillCredentialsStaysDisabled() { let appUserDefaults = AppUserDefaults(groupName: testGroupName) appUserDefaults.autofillCredentialsEnabled = false @@ -119,6 +156,25 @@ class AppUserDefaultsTests: XCTestCase { appUserDefaults.autofillCredentialsHasBeenEnabledAutomaticallyIfNecessary = true XCTAssertEqual(appUserDefaults.autofillCredentialsEnabled, false) } - */ + + + // MARK: - Mock Creation + + private func createFeatureFlagger(withSubfeatureEnabled enabled: Bool) -> DefaultFeatureFlagger { + let mockManager = MockPrivacyConfigurationManager() + mockManager.privacyConfig = mockConfiguration(subfeatureEnabled: enabled) + + let internalUserDecider = DefaultInternalUserDecider(store: internalUserDeciderStore) + return DefaultFeatureFlagger(internalUserDecider: internalUserDecider, privacyConfig: mockManager.privacyConfig) + } + + private func mockConfiguration(subfeatureEnabled: Bool) -> PrivacyConfiguration { + let mockPrivacyConfiguration = MockPrivacyConfiguration() + mockPrivacyConfiguration.isSubfeatureKeyEnabled = { _, _ in + return subfeatureEnabled + } + + return mockPrivacyConfiguration + } }