Skip to content

Commit

Permalink
Add FXIOS-10469 Firefox iOS: DAU Ping Setting (#24125)
Browse files Browse the repository at this point in the history
* FXIOS-10469 Firefox iOS: DAU Ping Setting

* Swiftlint fix

* Add and fix tests

* Fix registration

* Fix tests 2

* Fix tests 3

* Fix tests 3
  • Loading branch information
razvanlitianu authored Jan 16, 2025
1 parent a92522a commit ca92100
Show file tree
Hide file tree
Showing 13 changed files with 259 additions and 60 deletions.
4 changes: 4 additions & 0 deletions firefox-ios/Client.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -1117,6 +1117,7 @@
8C44A9D22A6A99FE009A1AA7 /* ShoppingProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C44A9D12A6A99FE009A1AA7 /* ShoppingProduct.swift */; };
8C46E1B72B2209F000F56521 /* FakespotAdsEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C46E1B62B2209F000F56521 /* FakespotAdsEvent.swift */; };
8C4B0F5D2C076B12008B3E74 /* UpdatableAddressFields+Decodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C4B0F5C2C076B12008B3E74 /* UpdatableAddressFields+Decodable.swift */; };
8C6AF26A2D38FB1500254698 /* GleanLifecycleObserverTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C6AF2692D38FB1500254698 /* GleanLifecycleObserverTests.swift */; };
8C6F94652A972EB300415FF6 /* FakespotAdjustRatingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C6F94632A972EB300415FF6 /* FakespotAdjustRatingView.swift */; };
8C6F94662A972EB300415FF6 /* FakespotStarRatingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C6F94642A972EB300415FF6 /* FakespotStarRatingView.swift */; };
8C8D8C7A2AA067AD00490D32 /* FakespotCoordinatorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C8D8C792AA067AD00490D32 /* FakespotCoordinatorTests.swift */; };
Expand Down Expand Up @@ -7949,6 +7950,7 @@
8C46E1B62B2209F000F56521 /* FakespotAdsEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FakespotAdsEvent.swift; sourceTree = "<group>"; };
8C4B0F5C2C076B12008B3E74 /* UpdatableAddressFields+Decodable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UpdatableAddressFields+Decodable.swift"; sourceTree = "<group>"; };
8C514AFEABEF8DE46CE7B8D4 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/AuthenticationManager.strings; sourceTree = "<group>"; };
8C6AF2692D38FB1500254698 /* GleanLifecycleObserverTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GleanLifecycleObserverTests.swift; sourceTree = "<group>"; };
8C6DA7D02A6FE78F00DE264F /* ShoppingProductTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShoppingProductTests.swift; sourceTree = "<group>"; };
8C6F94632A972EB300415FF6 /* FakespotAdjustRatingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FakespotAdjustRatingView.swift; sourceTree = "<group>"; };
8C6F94642A972EB300415FF6 /* FakespotStarRatingView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FakespotStarRatingView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -14372,6 +14374,7 @@
8A86DAD7277298DE00D7BFFF /* ClosedTabsStoreTests.swift */,
C2200A692B7D148C00DC062A /* ContentBlockerTests.swift */,
8CA4E80E2D22D2C7007207C1 /* GleanUsageReportingLifecycleObserverTest.swift */,
8C6AF2692D38FB1500254698 /* GleanLifecycleObserverTests.swift */,
C889D7CD2858C4B500121E1D /* ContextMenuHelperTests.swift */,
8A93F86329D37314004159D9 /* Coordinators */,
43B658D729CE249D00C9EF08 /* CreditCard */,
Expand Down Expand Up @@ -17500,6 +17503,7 @@
8A93F86529D37331004159D9 /* DefaultRouterTests.swift in Sources */,
5AD3B6802CF6674F00AFA1FE /* MockUIApplication.swift in Sources */,
8AF10D8A29D713F50086351D /* LaunchScreenViewModelTests.swift in Sources */,
8C6AF26A2D38FB1500254698 /* GleanLifecycleObserverTests.swift in Sources */,
8A87B4322CC1A3C0003A9239 /* PocketManagerTests.swift in Sources */,
2F13E79B1AC0C02700D75081 /* StringExtensionsTests.swift in Sources */,
CA24B52224ABD7D40093848C /* PasswordManagerViewModelTests.swift in Sources */,
Expand Down
1 change: 1 addition & 0 deletions firefox-ios/Client/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate, FeatureFlaggable {
lazy var backgroundTabLoader: BackgroundTabLoader = {
return DefaultBackgroundTabLoader(tabQueue: (AppContainer.shared.resolve() as Profile).queue)
}()
lazy var gleanLifecycleObserver = GleanLifecycleObserver()
private var isLoadingBackgroundTabs = false

private var shutdownWebServer: DispatchSourceTimer?
Expand Down
11 changes: 9 additions & 2 deletions firefox-ios/Client/Application/AppLaunchUtil.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,19 @@ class AppLaunchUtil {
private var profile: Profile
private let introScreenManager: IntroScreenManager
private let termsOfServiceManager: TermsOfServiceManager
private let gleanLifecycleObserver: GleanLifecycleObserver

init(logger: Logger = DefaultLogger.shared,
profile: Profile) {
init(
logger: Logger = DefaultLogger.shared,
profile: Profile,
gleanLifecycleObserver: GleanLifecycleObserver = AppContainer.shared.resolve()
) {
self.logger = logger
self.profile = profile
// self.adjustHelper = AdjustHelper(profile: profile)
self.introScreenManager = IntroScreenManager(prefs: profile.prefs)
self.termsOfServiceManager = TermsOfServiceManager(prefs: profile.prefs)
self.gleanLifecycleObserver = gleanLifecycleObserver
}

func setUpPreLaunchDependencies() {
Expand Down Expand Up @@ -52,6 +57,8 @@ class AppLaunchUtil {
TelemetryWrapper.shared.setup(profile: profile)
TelemetryWrapper.shared.recordStartUpTelemetry()
}

gleanLifecycleObserver.startObserving()
} else {
logger.setup(sendCrashReports: sendCrashReports)
TelemetryWrapper.shared.setup(profile: profile)
Expand Down
3 changes: 3 additions & 0 deletions firefox-ios/Client/Application/DependencyHelper.swift
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ class DependencyHelper {
)
AppContainer.shared.register(service: pocketManager)

let gleanLifecycleObserver: GleanLifecycleObserver = appDelegate.gleanLifecycleObserver
AppContainer.shared.register(service: gleanLifecycleObserver)

// Tell the container we are done registering
AppContainer.shared.bootstrap()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -442,7 +442,10 @@ class BrowserCoordinator: BaseCoordinator,
navigationController.modalPresentationStyle = modalPresentationStyle
let settingsRouter = DefaultRouter(navigationController: navigationController)

let settingsCoordinator = SettingsCoordinator(router: settingsRouter, tabManager: tabManager)
let settingsCoordinator = SettingsCoordinator(
router: settingsRouter,
tabManager: tabManager
)
settingsCoordinator.parentCoordinator = self
add(child: settingsCoordinator)
settingsCoordinator.start(with: section)
Expand Down
26 changes: 17 additions & 9 deletions firefox-ios/Client/Coordinators/SettingsCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,25 +28,33 @@ class SettingsCoordinator: BaseCoordinator,
private let profile: Profile
private let tabManager: TabManager
private let themeManager: ThemeManager
private let gleanLifecycleObserver: GleanLifecycleObserver
weak var parentCoordinator: SettingsCoordinatorDelegate?
private var windowUUID: WindowUUID { return tabManager.windowUUID }

init(router: Router,
wallpaperManager: WallpaperManagerInterface = WallpaperManager(),
profile: Profile = AppContainer.shared.resolve(),
tabManager: TabManager,
themeManager: ThemeManager = AppContainer.shared.resolve()) {
init(
router: Router,
wallpaperManager: WallpaperManagerInterface = WallpaperManager(),
profile: Profile = AppContainer.shared.resolve(),
tabManager: TabManager,
themeManager: ThemeManager = AppContainer.shared.resolve(),
gleanLifecycleObserver: GleanLifecycleObserver = AppContainer.shared.resolve()
) {
self.wallpaperManager = wallpaperManager
self.profile = profile
self.tabManager = tabManager
self.themeManager = themeManager
self.gleanLifecycleObserver = gleanLifecycleObserver
super.init(router: router)

// It's important we initialize AppSettingsTableViewController with a settingsDelegate and parentCoordinator
let settingsViewController = AppSettingsTableViewController(with: profile,
and: tabManager,
settingsDelegate: self,
parentCoordinator: self)
let settingsViewController = AppSettingsTableViewController(
with: profile,
and: tabManager,
settingsDelegate: self,
parentCoordinator: self,
gleanLifecycleObserver: gleanLifecycleObserver
)
self.settingsViewController = settingsViewController
router.setRootViewController(settingsViewController)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class AppSettingsTableViewController: SettingsTableViewController,
private var appAuthenticator: AppAuthenticationProtocol
private var applicationHelper: ApplicationHelper
private let logger: Logger
private let gleanLifecycleObserver: GleanLifecycleObserver

weak var parentCoordinator: SettingsFlowDelegate?

Expand All @@ -60,16 +61,20 @@ class AppSettingsTableViewController: SettingsTableViewController,
private var studiesToggleSetting: BoolSetting?

// MARK: - Initializers
init(with profile: Profile,
and tabManager: TabManager,
settingsDelegate: SettingsDelegate,
parentCoordinator: SettingsFlowDelegate,
appAuthenticator: AppAuthenticationProtocol = AppAuthenticator(),
applicationHelper: ApplicationHelper = DefaultApplicationHelper(),
logger: Logger = DefaultLogger.shared) {
init(
with profile: Profile,
and tabManager: TabManager,
settingsDelegate: SettingsDelegate,
parentCoordinator: SettingsFlowDelegate,
gleanLifecycleObserver: GleanLifecycleObserver,
appAuthenticator: AppAuthenticationProtocol = AppAuthenticator(),
applicationHelper: ApplicationHelper = DefaultApplicationHelper(),
logger: Logger = DefaultLogger.shared
) {
self.appAuthenticator = appAuthenticator
self.applicationHelper = applicationHelper
self.logger = logger
self.gleanLifecycleObserver = gleanLifecycleObserver

super.init(windowUUID: tabManager.windowUUID)
self.profile = profile
Expand Down Expand Up @@ -227,8 +232,12 @@ class AppSettingsTableViewController: SettingsTableViewController,
learnMoreURL: SupportUtils.URLForTopic("usage-ping-settings-mobile"),
isToSEnabled: isTermsOfServiceFeatureEnabled
)
sendDailyUsagePingSettings.shouldSendData = { value in
// TODO: FXIOS-10469 Firefox iOS: DAU Ping Setting
sendDailyUsagePingSettings.shouldSendData = { [weak self] value in
if value {
self?.gleanLifecycleObserver.startObserving()
} else {
self?.gleanLifecycleObserver.stopObserving()
}
}
sendDailyUsagePingSetting = sendDailyUsagePingSettings
} else {
Expand Down
38 changes: 28 additions & 10 deletions firefox-ios/Client/Telemetry/GleanUsageReporting.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,31 @@ import UIKit
import Glean
import Shared

enum UsageReason: String {
enum UsageReason: String, Equatable {
case active
case inactive
}

protocol GleanUsageReportingApi {
func setUsageReason(_ usageReason: UsageReason)
func submitPing()
func startTrackingDuration()
func stopTrackingDuration()
}

class GleanUsageReporting: GleanUsageReportingApi {
private var id: TimerId?

func startTrackingDuration() {
id = GleanMetrics.Usage.duration.start()
}

func stopTrackingDuration() {
if let timerId = id {
GleanMetrics.Usage.duration.stopAndAccumulate(timerId)
}
}

func setUsageReason(_ usageReason: UsageReason) {
GleanMetrics.Usage.reason.set(usageReason.rawValue)
}
Expand All @@ -41,25 +55,29 @@ class GleanUsageReporting: GleanUsageReportingApi {

class GleanLifecycleObserver {
private let gleanUsageReportingApi: GleanUsageReportingApi
private var id: TimerId?
private var isObserving = false
private var isObserving: Bool = false
private let notificationCenter: NotificationCenter

init(gleanUsageReportingApi: GleanUsageReportingApi = GleanUsageReporting()) {
init(
gleanUsageReportingApi: GleanUsageReportingApi = GleanUsageReporting(),
notificationCenter: NotificationCenter = .default
) {
self.gleanUsageReportingApi = gleanUsageReportingApi
self.notificationCenter = notificationCenter
}

func startObserving() {
guard !isObserving else { return }
isObserving = true

NotificationCenter.default.addObserver(
notificationCenter.addObserver(
self,
selector: #selector(appWillEnterForeground),
name: UIApplication.willEnterForegroundNotification,
object: nil
)

NotificationCenter.default.addObserver(
notificationCenter.addObserver(
self,
selector: #selector(appDidEnterBackground),
name: UIApplication.didEnterBackgroundNotification,
Expand All @@ -71,13 +89,13 @@ class GleanLifecycleObserver {
guard isObserving else { return }
isObserving = false

NotificationCenter.default.removeObserver(
notificationCenter.removeObserver(
self,
name: UIApplication.willEnterForegroundNotification,
object: nil
)

NotificationCenter.default.removeObserver(
notificationCenter.removeObserver(
self,
name: UIApplication.didEnterBackgroundNotification,
object: nil
Expand All @@ -95,13 +113,13 @@ class GleanLifecycleObserver {
}

func handleForegroundEvent() {
id = GleanMetrics.Usage.duration.start()
gleanUsageReportingApi.startTrackingDuration()
gleanUsageReportingApi.setUsageReason(.active)
gleanUsageReportingApi.submitPing()
}

func handleBackgroundEvent() {
id.map(GleanMetrics.Usage.duration.stopAndAccumulate)
gleanUsageReportingApi.stopTrackingDuration()
gleanUsageReportingApi.setUsageReason(.inactive)
gleanUsageReportingApi.submitPing()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -533,9 +533,11 @@ final class SettingsCoordinatorTests: XCTestCase {

// MARK: - Helper
func createSubject() -> SettingsCoordinator {
let subject = SettingsCoordinator(router: mockRouter,
wallpaperManager: wallpaperManager,
tabManager: MockTabManager()
let subject = SettingsCoordinator(
router: mockRouter,
wallpaperManager: wallpaperManager,
tabManager: MockTabManager(),
gleanLifecycleObserver: MockGleanLifecycleObserver()
)
trackForMemoryLeaks(subject)
return subject
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ class DependencyHelperMock {
let pocketManager: PocketManagerProvider = injectedPocketManager ?? MockPocketManager()
AppContainer.shared.register(service: pocketManager)

let gleanLifecycleObserver: GleanLifecycleObserver = MockGleanLifecycleObserver()
AppContainer.shared.register(service: gleanLifecycleObserver)

// Tell the container we are done registering
AppContainer.shared.bootstrap()
}
Expand Down
Loading

0 comments on commit ca92100

Please sign in to comment.