From 9bf9664199d9c5ea7367205180e9ee3c52a9aaa7 Mon Sep 17 00:00:00 2001 From: Marvin Liu Date: Wed, 5 Jul 2023 13:09:24 -0700 Subject: [PATCH] feat: add default events (#455) * feat: add app lifecycle events and screen view events * feat: add deep link helper * fix: swift auto-rename * feat: add tests, fix minor issues * fix: xcode file issue * fix: build issue * fix: flaky test * refactor: add save build/version check and move events to constant file --- Amplitude.xcodeproj/project.pbxproj | 56 +++++++ Framework/AmplitudeFramework.h | 1 + Sources/Amplitude/AMPConstants.h | 20 +++ Sources/Amplitude/AMPConstants.m | 20 +++ Sources/Amplitude/AMPDefaultTrackingOptions.m | 67 ++++++++ Sources/Amplitude/Amplitude.m | 138 +++++++++++++--- .../Public/AMPDefaultTrackingOptions.h | 61 +++++++ Sources/Amplitude/Public/Amplitude.h | 18 +- .../Amplitude/UIViewController+AMPScreen.h | 34 ++++ .../Amplitude/UIViewController+AMPScreen.m | 146 ++++++++++++++++ Tests/Amplitude+Test.h | 2 + Tests/Amplitude+Test.m | 9 + Tests/AmplitudeTests.m | 156 ++++++++++++++++++ Tests/DefaultTrackingOptionsTests.m | 58 +++++++ Tests/ScreenViewTests.m | 87 ++++++++++ Tests/SessionTests.m | 12 +- 16 files changed, 862 insertions(+), 23 deletions(-) create mode 100644 Sources/Amplitude/AMPDefaultTrackingOptions.m create mode 100644 Sources/Amplitude/Public/AMPDefaultTrackingOptions.h create mode 100644 Sources/Amplitude/UIViewController+AMPScreen.h create mode 100644 Sources/Amplitude/UIViewController+AMPScreen.m create mode 100644 Tests/DefaultTrackingOptionsTests.m create mode 100644 Tests/ScreenViewTests.m diff --git a/Amplitude.xcodeproj/project.pbxproj b/Amplitude.xcodeproj/project.pbxproj index d61a821a..f82386f6 100644 --- a/Amplitude.xcodeproj/project.pbxproj +++ b/Amplitude.xcodeproj/project.pbxproj @@ -175,6 +175,14 @@ 3EF6090327267C9800133703 /* AMPMiddleware.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EF6090127267C9800133703 /* AMPMiddleware.m */; }; 3EF6090427267C9800133703 /* AMPMiddleware.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EF6090127267C9800133703 /* AMPMiddleware.m */; }; 3EF6090527267C9800133703 /* AMPMiddleware.m in Sources */ = {isa = PBXBuildFile; fileRef = 3EF6090127267C9800133703 /* AMPMiddleware.m */; }; + 581FCD8F2A4D2A5C00AB0626 /* UIViewController+AMPScreen.h in Headers */ = {isa = PBXBuildFile; fileRef = 581FCD8D2A4D2A5C00AB0626 /* UIViewController+AMPScreen.h */; }; + 581FCD902A4D2A5C00AB0626 /* UIViewController+AMPScreen.h in Headers */ = {isa = PBXBuildFile; fileRef = 581FCD8D2A4D2A5C00AB0626 /* UIViewController+AMPScreen.h */; }; + 581FCD912A4D2A5C00AB0626 /* UIViewController+AMPScreen.h in Headers */ = {isa = PBXBuildFile; fileRef = 581FCD8D2A4D2A5C00AB0626 /* UIViewController+AMPScreen.h */; }; + 581FCD922A4D2A5C00AB0626 /* UIViewController+AMPScreen.h in Headers */ = {isa = PBXBuildFile; fileRef = 581FCD8D2A4D2A5C00AB0626 /* UIViewController+AMPScreen.h */; }; + 581FCD932A4D2A5C00AB0626 /* UIViewController+AMPScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 581FCD8E2A4D2A5C00AB0626 /* UIViewController+AMPScreen.m */; }; + 581FCD942A4D2A5C00AB0626 /* UIViewController+AMPScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 581FCD8E2A4D2A5C00AB0626 /* UIViewController+AMPScreen.m */; }; + 581FCD952A4D2A5C00AB0626 /* UIViewController+AMPScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 581FCD8E2A4D2A5C00AB0626 /* UIViewController+AMPScreen.m */; }; + 581FCD962A4D2A5C00AB0626 /* UIViewController+AMPScreen.m in Sources */ = {isa = PBXBuildFile; fileRef = 581FCD8E2A4D2A5C00AB0626 /* UIViewController+AMPScreen.m */; }; 582516E228C048D600ECAD0D /* AMPIngestionMetadata.h in Headers */ = {isa = PBXBuildFile; fileRef = 582516E128C048D600ECAD0D /* AMPIngestionMetadata.h */; settings = {ATTRIBUTES = (Public, ); }; }; 582516E328C048D600ECAD0D /* AMPIngestionMetadata.h in Headers */ = {isa = PBXBuildFile; fileRef = 582516E128C048D600ECAD0D /* AMPIngestionMetadata.h */; settings = {ATTRIBUTES = (Public, ); }; }; 582516E428C048D600ECAD0D /* AMPIngestionMetadata.h in Headers */ = {isa = PBXBuildFile; fileRef = 582516E128C048D600ECAD0D /* AMPIngestionMetadata.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -186,6 +194,20 @@ 582516F028C075D100ECAD0D /* IngestionMetadataTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 582516EB28C075C300ECAD0D /* IngestionMetadataTests.m */; }; 582516F128C075D200ECAD0D /* IngestionMetadataTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 582516EB28C075C300ECAD0D /* IngestionMetadataTests.m */; }; 582516F228C075D300ECAD0D /* IngestionMetadataTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 582516EB28C075C300ECAD0D /* IngestionMetadataTests.m */; }; + 58B7FACB2A3BD71F00CC5BB4 /* AMPDefaultTrackingOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 58B7FACA2A3BD71F00CC5BB4 /* AMPDefaultTrackingOptions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 58B7FACC2A3BD71F00CC5BB4 /* AMPDefaultTrackingOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 58B7FACA2A3BD71F00CC5BB4 /* AMPDefaultTrackingOptions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 58B7FACD2A3BD71F00CC5BB4 /* AMPDefaultTrackingOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 58B7FACA2A3BD71F00CC5BB4 /* AMPDefaultTrackingOptions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 58B7FACE2A3BD71F00CC5BB4 /* AMPDefaultTrackingOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 58B7FACA2A3BD71F00CC5BB4 /* AMPDefaultTrackingOptions.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 58B7FAD02A3CDC7E00CC5BB4 /* AMPDefaultTrackingOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B7FACF2A3CDC7E00CC5BB4 /* AMPDefaultTrackingOptions.m */; }; + 58B7FAD12A3CDC7E00CC5BB4 /* AMPDefaultTrackingOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B7FACF2A3CDC7E00CC5BB4 /* AMPDefaultTrackingOptions.m */; }; + 58B7FAD22A3CDC7E00CC5BB4 /* AMPDefaultTrackingOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B7FACF2A3CDC7E00CC5BB4 /* AMPDefaultTrackingOptions.m */; }; + 58B7FAD32A3CDC7E00CC5BB4 /* AMPDefaultTrackingOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B7FACF2A3CDC7E00CC5BB4 /* AMPDefaultTrackingOptions.m */; }; + 58B7FAD92A3D0D6000CC5BB4 /* DefaultTrackingOptionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B7FAD42A3D0D5500CC5BB4 /* DefaultTrackingOptionsTests.m */; }; + 58B7FADA2A3D0D6100CC5BB4 /* DefaultTrackingOptionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B7FAD42A3D0D5500CC5BB4 /* DefaultTrackingOptionsTests.m */; }; + 58B7FADB2A3D0D6200CC5BB4 /* DefaultTrackingOptionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B7FAD42A3D0D5500CC5BB4 /* DefaultTrackingOptionsTests.m */; }; + 58D45C322A4CDC7C00A090A3 /* ScreenViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 58D45C312A4CDC7C00A090A3 /* ScreenViewTests.m */; }; + 58D45C332A4CDC7C00A090A3 /* ScreenViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 58D45C312A4CDC7C00A090A3 /* ScreenViewTests.m */; }; + 58D45C342A4CDC7C00A090A3 /* ScreenViewTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 58D45C312A4CDC7C00A090A3 /* ScreenViewTests.m */; }; 58E4B1BC287F62C3007AC408 /* AmazonRootCA1.cer in Resources */ = {isa = PBXBuildFile; fileRef = 58E4B1BB287F62C3007AC408 /* AmazonRootCA1.cer */; }; 58E4B1BD287F62C3007AC408 /* AmazonRootCA1.cer in Resources */ = {isa = PBXBuildFile; fileRef = 58E4B1BB287F62C3007AC408 /* AmazonRootCA1.cer */; }; 58E4B1BE287F62C3007AC408 /* AmazonRootCA1.cer in Resources */ = {isa = PBXBuildFile; fileRef = 58E4B1BB287F62C3007AC408 /* AmazonRootCA1.cer */; }; @@ -362,9 +384,15 @@ 3EF6090127267C9800133703 /* AMPMiddleware.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AMPMiddleware.m; sourceTree = ""; }; 3F7002FB71B21CB2ABA065F1 /* Pods-shared-Amplitude_macOSTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-shared-Amplitude_macOSTests.debug.xcconfig"; path = "Target Support Files/Pods-shared-Amplitude_macOSTests/Pods-shared-Amplitude_macOSTests.debug.xcconfig"; sourceTree = ""; }; 4D8EA610D8DF8BD11DB40E7A /* Pods-shared-Amplitude_tvOSTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-shared-Amplitude_tvOSTests.debug.xcconfig"; path = "Target Support Files/Pods-shared-Amplitude_tvOSTests/Pods-shared-Amplitude_tvOSTests.debug.xcconfig"; sourceTree = ""; }; + 581FCD8D2A4D2A5C00AB0626 /* UIViewController+AMPScreen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIViewController+AMPScreen.h"; sourceTree = ""; }; + 581FCD8E2A4D2A5C00AB0626 /* UIViewController+AMPScreen.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+AMPScreen.m"; sourceTree = ""; }; 582516E128C048D600ECAD0D /* AMPIngestionMetadata.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AMPIngestionMetadata.h; sourceTree = ""; }; 582516E628C048E700ECAD0D /* AMPIngestionMetadata.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AMPIngestionMetadata.m; sourceTree = ""; }; 582516EB28C075C300ECAD0D /* IngestionMetadataTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = IngestionMetadataTests.m; sourceTree = ""; }; + 58B7FACA2A3BD71F00CC5BB4 /* AMPDefaultTrackingOptions.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AMPDefaultTrackingOptions.h; sourceTree = ""; }; + 58B7FACF2A3CDC7E00CC5BB4 /* AMPDefaultTrackingOptions.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AMPDefaultTrackingOptions.m; sourceTree = ""; }; + 58B7FAD42A3D0D5500CC5BB4 /* DefaultTrackingOptionsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DefaultTrackingOptionsTests.m; sourceTree = ""; }; + 58D45C312A4CDC7C00A090A3 /* ScreenViewTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ScreenViewTests.m; sourceTree = ""; }; 58E4B1BB287F62C3007AC408 /* AmazonRootCA1.cer */ = {isa = PBXFileReference; lastKnownFileType = file; path = AmazonRootCA1.cer; sourceTree = ""; }; 6C55E7B2C7CB09D0EDC07910 /* Pods-shared-Amplitude_iOSTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-shared-Amplitude_iOSTests.release.xcconfig"; path = "Target Support Files/Pods-shared-Amplitude_iOSTests/Pods-shared-Amplitude_iOSTests.release.xcconfig"; sourceTree = ""; }; 70CE71E7AEFA4FE3E4F565AD /* Pods-shared-Amplitude_iOSTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-shared-Amplitude_iOSTests.debug.xcconfig"; path = "Target Support Files/Pods-shared-Amplitude_iOSTests/Pods-shared-Amplitude_iOSTests.debug.xcconfig"; sourceTree = ""; }; @@ -508,6 +536,8 @@ 1279F8E625244D78003DCE07 /* AMPConstants.m */, 1279F8C325244D78003DCE07 /* AMPDatabaseHelper.h */, 1279F8D425244D78003DCE07 /* AMPDatabaseHelper.m */, + 581FCD8D2A4D2A5C00AB0626 /* UIViewController+AMPScreen.h */, + 581FCD8E2A4D2A5C00AB0626 /* UIViewController+AMPScreen.m */, 1279F8C225244D78003DCE07 /* AMPDeviceInfo.h */, 1279F8D525244D78003DCE07 /* AMPDeviceInfo.m */, 1279F8D625244D78003DCE07 /* AMPIdentify.m */, @@ -530,6 +560,7 @@ 3EF608EE2726256800133703 /* AMPMiddlewareRunner.m */, 3EF6090127267C9800133703 /* AMPMiddleware.m */, 582516E628C048E700ECAD0D /* AMPIngestionMetadata.m */, + 58B7FACF2A3CDC7E00CC5BB4 /* AMPDefaultTrackingOptions.m */, ); path = Amplitude; sourceTree = ""; @@ -572,6 +603,8 @@ 3EF608E027211F8A00133703 /* ConfigManagerTests.m */, 3EF608FD272666E300133703 /* MiddlewareRunnerTests.m */, 582516EB28C075C300ECAD0D /* IngestionMetadataTests.m */, + 58B7FAD42A3D0D5500CC5BB4 /* DefaultTrackingOptionsTests.m */, + 58D45C312A4CDC7C00A090A3 /* ScreenViewTests.m */, ); path = Tests; sourceTree = ""; @@ -594,6 +627,7 @@ 3EF608C82720E74D00133703 /* AMPServerZone.h */, 3EF608E42724BFB700133703 /* AMPMiddleware.h */, 582516E128C048D600ECAD0D /* AMPIngestionMetadata.h */, + 58B7FACA2A3BD71F00CC5BB4 /* AMPDefaultTrackingOptions.h */, ); path = Public; sourceTree = ""; @@ -669,12 +703,14 @@ 1279F92325244E8E003DCE07 /* AMPRevenue.h in Headers */, 1279F91325244E8E003DCE07 /* AMPURLSession.h in Headers */, D00A34DC298DA0EC00BA484F /* AMPIdentifyInterceptor.h in Headers */, + 581FCD902A4D2A5C00AB0626 /* UIViewController+AMPScreen.h in Headers */, 1279F91025244E8E003DCE07 /* AMPDeviceInfo.h in Headers */, 1279F91125244E8E003DCE07 /* AMPIdentify.h in Headers */, 1279F92025244E8E003DCE07 /* AMPDatabaseHelper.h in Headers */, 1279F91725244E8E003DCE07 /* AMPConfigManager.h in Headers */, 1279F91625244E8E003DCE07 /* AMPUtils.h in Headers */, 582516E328C048D600ECAD0D /* AMPIngestionMetadata.h in Headers */, + 58B7FACC2A3BD71F00CC5BB4 /* AMPDefaultTrackingOptions.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -700,12 +736,14 @@ 1279F90525244E8D003DCE07 /* AMPRevenue.h in Headers */, 1279F8F525244E8D003DCE07 /* AMPURLSession.h in Headers */, D00A34DB298DA0EC00BA484F /* AMPIdentifyInterceptor.h in Headers */, + 581FCD8F2A4D2A5C00AB0626 /* UIViewController+AMPScreen.h in Headers */, 1279F8F225244E8D003DCE07 /* AMPDeviceInfo.h in Headers */, 1279F8F325244E8D003DCE07 /* AMPIdentify.h in Headers */, 1279F90225244E8D003DCE07 /* AMPDatabaseHelper.h in Headers */, 1279F8F925244E8D003DCE07 /* AMPConfigManager.h in Headers */, 1279F8F825244E8D003DCE07 /* AMPUtils.h in Headers */, 582516E228C048D600ECAD0D /* AMPIngestionMetadata.h in Headers */, + 58B7FACB2A3BD71F00CC5BB4 /* AMPDefaultTrackingOptions.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -731,12 +769,14 @@ 1279F94125244E8F003DCE07 /* AMPRevenue.h in Headers */, 1279F93125244E8F003DCE07 /* AMPURLSession.h in Headers */, D00A34DD298DA0EC00BA484F /* AMPIdentifyInterceptor.h in Headers */, + 581FCD912A4D2A5C00AB0626 /* UIViewController+AMPScreen.h in Headers */, 1279F92E25244E8F003DCE07 /* AMPDeviceInfo.h in Headers */, 1279F92F25244E8F003DCE07 /* AMPIdentify.h in Headers */, 1279F93E25244E8F003DCE07 /* AMPDatabaseHelper.h in Headers */, 1279F93525244E8F003DCE07 /* AMPConfigManager.h in Headers */, 1279F93425244E8F003DCE07 /* AMPUtils.h in Headers */, 582516E428C048D600ECAD0D /* AMPIngestionMetadata.h in Headers */, + 58B7FACD2A3BD71F00CC5BB4 /* AMPDefaultTrackingOptions.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -762,12 +802,14 @@ 3EF608D62720F52400133703 /* AMPServerZoneUtil.h in Headers */, 759E93AC25FBF44500BF7C3D /* AmplitudePrivate.h in Headers */, D00A34DE298DA0EC00BA484F /* AMPIdentifyInterceptor.h in Headers */, + 581FCD922A4D2A5C00AB0626 /* UIViewController+AMPScreen.h in Headers */, 759E93B025FBF44500BF7C3D /* AMPURLSession.h in Headers */, 759E93A825FBF44500BF7C3D /* AMPDeviceInfo.h in Headers */, 759E93A725FBF44500BF7C3D /* AMPDatabaseHelper.h in Headers */, 759E93A525FBF44500BF7C3D /* AMPConfigManager.h in Headers */, 759E93B125FBF44500BF7C3D /* AMPUtils.h in Headers */, 582516E528C048D600ECAD0D /* AMPIngestionMetadata.h in Headers */, + 58B7FACE2A3BD71F00CC5BB4 /* AMPDefaultTrackingOptions.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1113,6 +1155,7 @@ 759E941125FBFE7800BF7C3D /* AMPBackgroundNotifier.m in Sources */, 1279FA6F2525949D003DCE07 /* ISPCertificatePinning.m in Sources */, 582516E828C048E700ECAD0D /* AMPIngestionMetadata.m in Sources */, + 581FCD942A4D2A5C00AB0626 /* UIViewController+AMPScreen.m in Sources */, 3EF608F02726256800133703 /* AMPMiddlewareRunner.m in Sources */, 1279F91A25244E8E003DCE07 /* AMPUtils.m in Sources */, 3EF6090327267C9800133703 /* AMPMiddleware.m in Sources */, @@ -1127,6 +1170,7 @@ 1279F91D25244E8E003DCE07 /* AMPTrackingOptions.m in Sources */, 1279FA782525949D003DCE07 /* ISPPinnedNSURLSessionDelegate.m in Sources */, 3E2411ED26F9A46500793829 /* AMPPlan.m in Sources */, + 58B7FAD12A3CDC7E00CC5BB4 /* AMPDefaultTrackingOptions.m in Sources */, 3EF608D92720F64500133703 /* AMPServerZoneUtil.m in Sources */, 1279F91C25244E8E003DCE07 /* AMPRevenue.m in Sources */, 1279F91F25244E8E003DCE07 /* AMPURLSession.m in Sources */, @@ -1140,6 +1184,7 @@ 12C973B6241244C600E9CDDB /* SetupTests.m in Sources */, 12C973C3241244F000E9CDDB /* IdentifyTests.m in Sources */, 12C973A7241244A600E9CDDB /* DeviceInfoTests.m in Sources */, + 58B7FADA2A3D0D6100CC5BB4 /* DefaultTrackingOptionsTests.m in Sources */, 12C973B1241244BF00E9CDDB /* AMPDatabaseHelperTests.m in Sources */, 58E4B1C1287F6FD7007AC408 /* SSLPinningTests.m in Sources */, 12C973C8241244F800E9CDDB /* AmplitudeTVOSTests.m in Sources */, @@ -1154,6 +1199,7 @@ 3EF608E227211F8A00133703 /* ConfigManagerTests.m in Sources */, 12C973C2241244F000E9CDDB /* TrackingOptionsTest.m in Sources */, 3EF608DE27211AC000133703 /* ServerZoneUtilTests.m in Sources */, + 58D45C332A4CDC7C00A090A3 /* ScreenViewTests.m in Sources */, 582516F128C075D200ECAD0D /* IngestionMetadataTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1165,6 +1211,7 @@ 759E941925FBFE7900BF7C3D /* AMPBackgroundNotifier.m in Sources */, 1279F8FE25244E8D003DCE07 /* AMPRevenue.m in Sources */, 582516E728C048E700ECAD0D /* AMPIngestionMetadata.m in Sources */, + 581FCD932A4D2A5C00AB0626 /* UIViewController+AMPScreen.m in Sources */, 1279F90825244E8D003DCE07 /* AMPDatabaseHelper.m in Sources */, 3EF608D82720F64500133703 /* AMPServerZoneUtil.m in Sources */, 3E2411EC26F9A46500793829 /* AMPPlan.m in Sources */, @@ -1179,6 +1226,7 @@ 1279FA772525949D003DCE07 /* ISPPinnedNSURLSessionDelegate.m in Sources */, 1279FA6E2525949D003DCE07 /* ISPCertificatePinning.m in Sources */, 3EF6090227267C9800133703 /* AMPMiddleware.m in Sources */, + 58B7FAD02A3CDC7E00CC5BB4 /* AMPDefaultTrackingOptions.m in Sources */, 1279F90725244E8D003DCE07 /* AMPDeviceInfo.m in Sources */, 1279F90425244E8D003DCE07 /* Amplitude.m in Sources */, 3EF608EF2726256800133703 /* AMPMiddlewareRunner.m in Sources */, @@ -1192,6 +1240,7 @@ 12C973B5241244C500E9CDDB /* SetupTests.m in Sources */, 12DF9471251DAC27008B2C25 /* AmplitudeiOSTests.m in Sources */, 12C973C0241244EF00E9CDDB /* IdentifyTests.m in Sources */, + 58B7FAD92A3D0D6000CC5BB4 /* DefaultTrackingOptionsTests.m in Sources */, 19619D9628A247DF00A2CC53 /* AmplitudeTests.m in Sources */, 12C973A6241244A400E9CDDB /* DeviceInfoTests.m in Sources */, 58E4B1C0287F6FD6007AC408 /* SSLPinningTests.m in Sources */, @@ -1206,6 +1255,7 @@ 3EF608E127211F8A00133703 /* ConfigManagerTests.m in Sources */, 12C973BF241244EF00E9CDDB /* TrackingOptionsTest.m in Sources */, 3EF608DD27211AC000133703 /* ServerZoneUtilTests.m in Sources */, + 58D45C322A4CDC7C00A090A3 /* ScreenViewTests.m in Sources */, 582516F028C075D100ECAD0D /* IngestionMetadataTests.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1217,6 +1267,7 @@ 759E940925FBFE7700BF7C3D /* AMPBackgroundNotifier.m in Sources */, 1279FA702525949D003DCE07 /* ISPCertificatePinning.m in Sources */, 582516E928C048E700ECAD0D /* AMPIngestionMetadata.m in Sources */, + 581FCD952A4D2A5C00AB0626 /* UIViewController+AMPScreen.m in Sources */, 3EF608F12726256800133703 /* AMPMiddlewareRunner.m in Sources */, 1279F93825244E8F003DCE07 /* AMPUtils.m in Sources */, 3EF6090427267C9800133703 /* AMPMiddleware.m in Sources */, @@ -1231,6 +1282,7 @@ 1279F93B25244E8F003DCE07 /* AMPTrackingOptions.m in Sources */, 1279FA792525949D003DCE07 /* ISPPinnedNSURLSessionDelegate.m in Sources */, 3E2411EE26F9A46500793829 /* AMPPlan.m in Sources */, + 58B7FAD22A3CDC7E00CC5BB4 /* AMPDefaultTrackingOptions.m in Sources */, 3EF608DA2720F64500133703 /* AMPServerZoneUtil.m in Sources */, 1279F93A25244E8F003DCE07 /* AMPRevenue.m in Sources */, 1279F93D25244E8F003DCE07 /* AMPURLSession.m in Sources */, @@ -1244,6 +1296,7 @@ 12C973B7241244C700E9CDDB /* SetupTests.m in Sources */, 3E2411F726F9A4E400793829 /* PlanTests.m in Sources */, 12C973C6241244F100E9CDDB /* IdentifyTests.m in Sources */, + 58B7FADB2A3D0D6200CC5BB4 /* DefaultTrackingOptionsTests.m in Sources */, 12C973A8241244A700E9CDDB /* DeviceInfoTests.m in Sources */, 12C973B3241244BF00E9CDDB /* AMPDatabaseHelperTests.m in Sources */, 58E4B1C2287F6FD7007AC408 /* SSLPinningTests.m in Sources */, @@ -1255,6 +1308,7 @@ 12C973C7241244F100E9CDDB /* BaseTestCase.m in Sources */, 12C973BE241244E500E9CDDB /* SessionTests.m in Sources */, 12C973AE241244B800E9CDDB /* AmplitudeTests.m in Sources */, + 58D45C342A4CDC7C00A090A3 /* ScreenViewTests.m in Sources */, 12C973C5241244F100E9CDDB /* TrackingOptionsTest.m in Sources */, 3EF608DF27211AC000133703 /* ServerZoneUtilTests.m in Sources */, 3EF60900272666E400133703 /* MiddlewareRunnerTests.m in Sources */, @@ -1268,6 +1322,7 @@ 759E93DD25FBF6F700BF7C3D /* AMPBackgroundNotifier.m in Sources */, 759E939025FBF3DC00BF7C3D /* AMPConfigManager.m in Sources */, 582516EA28C048E700ECAD0D /* AMPIngestionMetadata.m in Sources */, + 581FCD962A4D2A5C00AB0626 /* UIViewController+AMPScreen.m in Sources */, 3EF608F22726256800133703 /* AMPMiddlewareRunner.m in Sources */, 759E939125FBF3DC00BF7C3D /* AMPConstants.m in Sources */, 759E939225FBF3DC00BF7C3D /* AMPDatabaseHelper.m in Sources */, @@ -1282,6 +1337,7 @@ 759E939925FBF3DC00BF7C3D /* AMPURLSession.m in Sources */, 759E939A25FBF3DC00BF7C3D /* AMPUtils.m in Sources */, 759E939B25FBF3DC00BF7C3D /* ISPCertificatePinning.m in Sources */, + 58B7FAD32A3CDC7E00CC5BB4 /* AMPDefaultTrackingOptions.m in Sources */, 3E2411EF26F9A46500793829 /* AMPPlan.m in Sources */, 3EF608DB2720F64500133703 /* AMPServerZoneUtil.m in Sources */, 759E939D25FBF3DC00BF7C3D /* ISPPinnedNSURLSessionDelegate.m in Sources */, diff --git a/Framework/AmplitudeFramework.h b/Framework/AmplitudeFramework.h index fad62717..381b46e0 100644 --- a/Framework/AmplitudeFramework.h +++ b/Framework/AmplitudeFramework.h @@ -8,6 +8,7 @@ #import #import #import +#import #if TARGET_OS_WATCH #import diff --git a/Sources/Amplitude/AMPConstants.h b/Sources/Amplitude/AMPConstants.h index 169a53b3..70ab3a51 100644 --- a/Sources/Amplitude/AMPConstants.h +++ b/Sources/Amplitude/AMPConstants.h @@ -98,3 +98,23 @@ extern NSString *const AMP_PLAN_VERSION_ID; // Ingestion Metadata extern NSString *const AMP_INGESTION_METADATA_SOURCE_NAME; extern NSString *const AMP_INGESTION_METADATA_SOURCE_VERSION; + +// Events +extern NSString *const kAMPSessionStartEvent; +extern NSString *const kAMPSessionEndEvent; +extern NSString *const kAMPApplicationInstalled; +extern NSString *const kAMPApplicationUpdated; +extern NSString *const kAMPApplicationOpened; +extern NSString *const kAMPApplicationBackgrounded; +extern NSString *const kAMPDeepLinkOpened; +extern NSString *const kAMPScreenViewed; +extern NSString *const kAMPRevenueEvent; + +extern NSString *const kAMPEventPropVersion; +extern NSString *const kAMPEventPropBuild; +extern NSString *const kAMPEventPropPreviousVersion; +extern NSString *const kAMPEventPropPreviousBuild; +extern NSString *const kAMPEventPropFromBackground; +extern NSString *const kAMPEventPropLinkUrl; +extern NSString *const kAMPEventPropLinkReferrer; +extern NSString *const kAMPEventPropScreenName; diff --git a/Sources/Amplitude/AMPConstants.m b/Sources/Amplitude/AMPConstants.m index 718c2fa1..e8413b36 100644 --- a/Sources/Amplitude/AMPConstants.m +++ b/Sources/Amplitude/AMPConstants.m @@ -118,3 +118,23 @@ NSString *const AMP_INGESTION_METADATA_SOURCE_NAME = @"source_name"; NSString *const AMP_INGESTION_METADATA_SOURCE_VERSION = @"source_version"; + +// Amplitude Events +NSString *const kAMPSessionStartEvent = @"session_start"; +NSString *const kAMPSessionEndEvent = @"session_end"; +NSString *const kAMPApplicationInstalled = @"[Amplitude] Application Installed"; +NSString *const kAMPApplicationUpdated = @"[Amplitude] Application Updated"; +NSString *const kAMPApplicationOpened = @"[Amplitude] Application Opened"; +NSString *const kAMPApplicationBackgrounded = @"[Amplitude] Application Backgrounded"; +NSString *const kAMPDeepLinkOpened = @"[Amplitude] Deep Link Opened"; +NSString *const kAMPScreenViewed = @"[Amplitude] Screen Viewed"; +NSString *const kAMPRevenueEvent = @"revenue_amount"; + +NSString *const kAMPEventPropVersion = @"[Amplitude] Version"; +NSString *const kAMPEventPropBuild = @"[Amplitude] Build"; +NSString *const kAMPEventPropPreviousVersion = @"[Amplitude] Previous Version"; +NSString *const kAMPEventPropPreviousBuild = @"[Amplitude] Previous Build"; +NSString *const kAMPEventPropFromBackground = @"[Amplitude] From Background"; +NSString *const kAMPEventPropLinkUrl = @"[Amplitude] Link URL"; +NSString *const kAMPEventPropLinkReferrer = @"[Amplitude] Link Referrer"; +NSString *const kAMPEventPropScreenName = @"[Amplitude] Screen Name"; diff --git a/Sources/Amplitude/AMPDefaultTrackingOptions.m b/Sources/Amplitude/AMPDefaultTrackingOptions.m new file mode 100644 index 00000000..3d4041a7 --- /dev/null +++ b/Sources/Amplitude/AMPDefaultTrackingOptions.m @@ -0,0 +1,67 @@ +// +// AMPDefaultTrackingOptions.m +// Copyright (c) 2023 Amplitude Inc. (https://amplitude.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#import "AMPDefaultTrackingOptions.h" + +@implementation AMPDefaultTrackingOptions + +/* + * Create an AMPDefaultTrackingOptions object + */ +- (instancetype)init { + if (self = [super init]) { + self.sessions = NO; + self.appLifecycles = NO; + self.deepLinks = NO; + self.screenViews = NO; + } + return self; +} + ++ (instancetype)initWithSessions:(BOOL)sessions + appLifecycles:(BOOL)appLifecycles + deepLinks:(BOOL)deepLinks + screenViews:(BOOL)screenViews { + AMPDefaultTrackingOptions *instance = [[self alloc] init]; + instance.sessions = sessions; + instance.appLifecycles = appLifecycles; + instance.deepLinks = deepLinks; + instance.screenViews = screenViews; + return instance; +} + ++ (instancetype)initWithAllEnabled { + return [self initWithSessions:YES + appLifecycles:YES + deepLinks:YES + screenViews:YES]; +} + ++ (instancetype)initWithNoneEnabled { + return [self initWithSessions:NO + appLifecycles:NO + deepLinks:NO + screenViews:NO]; +} + +@end diff --git a/Sources/Amplitude/Amplitude.m b/Sources/Amplitude/Amplitude.m index e788c92b..a16e3fcd 100644 --- a/Sources/Amplitude/Amplitude.m +++ b/Sources/Amplitude/Amplitude.m @@ -66,6 +66,11 @@ #import "AMPMiddlewareRunner.h" #import "AMPIdentifyInterceptor.h" #import "AMPEventUtils.h" + +#if !TARGET_OS_OSX && !TARGET_OS_WATCH +#import "UIViewController+AMPScreen.h" +#endif + #import #import @@ -101,10 +106,6 @@ @interface Amplitude () @property (nonatomic, copy, readwrite) NSString *contentTypeHeader; @end -NSString *const kAMPSessionStartEvent = @"session_start"; -NSString *const kAMPSessionEndEvent = @"session_end"; -NSString *const kAMPRevenueEvent = @"revenue_amount"; - static NSString *const BACKGROUND_QUEUE_NAME = @"BACKGROUND"; static NSString *const DATABASE_VERSION = @"database_version"; static NSString *const DEVICE_ID = @"device_id"; @@ -117,6 +118,9 @@ @interface Amplitude () static NSString *const OPT_OUT = @"opt_out"; static NSString *const USER_ID = @"user_id"; static NSString *const SEQUENCE_NUMBER = @"sequence_number"; +// for app lifecycle events +static NSString *const APP_VERSION = @"app_version"; +static NSString *const APP_BUILD = @"app_build"; @implementation Amplitude { @@ -240,6 +244,8 @@ - (instancetype)initWithInstanceName:(NSString *)instanceName { [[[AnalyticsConnector getInstance:self.instanceName] eventBridge] setEventReceiver:^(AnalyticsEvent * _Nonnull event) { [self logEvent:[event eventType] withEventProperties:[event eventProperties] withApiProperties:nil withUserProperties:[event userProperties] withGroups:nil withGroupProperties:nil withTimestamp:nil outOfSession:false]; }]; + + self.defaultTracking = [[AMPDefaultTrackingOptions alloc] init]; _initializerQueue = [[NSOperationQueue alloc] init]; _backgroundQueue = [[NSOperationQueue alloc] init]; @@ -403,20 +409,75 @@ - (void)addObservers { name:NSApplicationDidResignActiveNotification object:nil]; #endif + +#if !TARGET_OS_OSX && !TARGET_OS_WATCH + // mount the default events handler + UIApplication *app = [AMPUtils getSharedApplication]; + if (app) { + for (NSString *name in @[UIApplicationDidEnterBackgroundNotification, + UIApplicationDidFinishLaunchingNotification, + UIApplicationWillEnterForegroundNotification]) { + [center addObserver:self selector:@selector(handleAppStateUpdates:) name:name object:app]; + } + } +#endif +} + +#if !TARGET_OS_OSX && !TARGET_OS_WATCH +- (void)handleAppStateUpdates:(NSNotification *)notification { + // lazy checking the settings here to avoid early init with false value + if (!self.defaultTracking.appLifecycles) { + return; + } + if ([notification.name isEqualToString:UIApplicationDidFinishLaunchingNotification]) { + NSString *previousBuild = [_dbHelper getValue:APP_BUILD]; + NSString *previousVersion = [_dbHelper getValue:APP_VERSION]; + NSString *currentBuild = [[NSBundle mainBundle] infoDictionary][@"CFBundleVersion"]; + NSString *currentVersion = [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"]; + if (!previousBuild) { + [self logEvent:kAMPApplicationInstalled withEventProperties:@{ + kAMPEventPropBuild: currentBuild ?: @"", + kAMPEventPropVersion: currentVersion ?: @"", + }]; + } else if (![currentBuild isEqualToString:previousBuild]) { + [self logEvent:kAMPApplicationUpdated withEventProperties:@{ + kAMPEventPropBuild: currentBuild ?: @"", + kAMPEventPropVersion: currentVersion ?: @"", + kAMPEventPropPreviousBuild: previousBuild ?: @"", + kAMPEventPropPreviousVersion: previousVersion ?: @"", + }]; + } + [self logEvent:kAMPApplicationOpened withEventProperties:@{ + kAMPEventPropBuild: currentBuild ?: @"", + kAMPEventPropVersion: currentVersion ?: @"", + kAMPEventPropFromBackground: @NO, + }]; + + // persist the build/version when changed + if (currentBuild ? ![currentBuild isEqualToString:previousBuild] : (previousBuild != nil)) { + [_dbHelper insertOrReplaceKeyValue:APP_BUILD value:currentBuild]; + } + if (currentVersion ? ![currentVersion isEqualToString:previousVersion] : (previousVersion != nil)) { + [_dbHelper insertOrReplaceKeyValue:APP_VERSION value:currentVersion]; + } + } else if ([notification.name isEqualToString:UIApplicationWillEnterForegroundNotification]) { + NSString *currentBuild = [[NSBundle mainBundle] infoDictionary][@"CFBundleVersion"]; + NSString *currentVersion = [[NSBundle mainBundle] infoDictionary][@"CFBundleShortVersionString"]; + [self logEvent:kAMPApplicationOpened withEventProperties:@{ + kAMPEventPropBuild: currentBuild ?: @"", + kAMPEventPropVersion: currentVersion ?: @"", + kAMPEventPropFromBackground: @YES, + }]; + } else if ([notification.name isEqualToString:UIApplicationDidEnterBackgroundNotification]) { + [self logEvent:kAMPApplicationBackgrounded]; + } } +#endif - (void)removeObservers { NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; -#if TARGET_OS_WATCH - [center removeObserver:self name:AMPAppWillEnterForegroundNotification object:nil]; - [center removeObserver:self name:AMPAppDidEnterBackgroundNotification object:nil]; -#elif !TARGET_OS_OSX - [center removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil]; - [center removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil]; -#else - [center removeObserver:self name:NSApplicationDidBecomeActiveNotification object:nil]; - [center removeObserver:self name:NSApplicationDidResignActiveNotification object:nil]; -#endif + // unregister all observers added by addObservers method + [center removeObserver:self]; } - (void)dealloc { @@ -477,6 +538,13 @@ - (void)initializeApiKey:(NSString *)apiKey if (self.initCompletionBlock != nil) { self.initCompletionBlock(); } + +#if !TARGET_OS_OSX && !TARGET_OS_WATCH + // Unlike other default events options that can be evaluated later, screenViews has to be evaluated during the actual initialization + if (self.defaultTracking.screenViews) { + [UIViewController amp_swizzleViewDidAppear]; + } +#endif }]; if (!self.deferCheckInForeground) { @@ -622,7 +690,7 @@ - (void)logEvent:(NSString *)eventType withEventProperties:(NSDictionary *)event } // skip session check if logging start_session or end_session events - BOOL loggingSessionEvent = self->_trackingSessionEvents && ([eventType isEqualToString:kAMPSessionStartEvent] || [eventType isEqualToString:kAMPSessionEndEvent]); + BOOL loggingSessionEvent = (self->_trackingSessionEvents || self.defaultTracking.sessions) && ([eventType isEqualToString:kAMPSessionStartEvent] || [eventType isEqualToString:kAMPSessionEndEvent]); if (!loggingSessionEvent && !outOfSession) { [self startOrContinueSessionNSNumber:timestamp inForeground:inForeground]; } @@ -833,6 +901,36 @@ - (void)logRevenueV2:(AMPRevenue *)revenue { [self logEvent:kAMPRevenueEvent withEventProperties:[revenue toNSDictionary]]; } +#pragma mark - Deep link methods +- (void)continueUserActivity:(NSUserActivity *)activity { + if (!self.defaultTracking.deepLinks) { + return; + } + + if ([activity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { + NSString *urlString = activity.webpageURL.absoluteString; + NSString *referrerString = nil; + if (@available(iOS 11, tvOS 11.0, macOS 10.13, watchOS 4.0, *)) { + referrerString = activity.referrerURL.absoluteString; + } + [self logEvent:kAMPDeepLinkOpened withEventProperties:@{ + kAMPEventPropLinkUrl: urlString ?: @"", + kAMPEventPropLinkReferrer: referrerString ?: @"", + }]; + } +} + +- (void)openURL:(NSURL *)url { + if (!self.defaultTracking.deepLinks) { + return; + } + + NSString *urlString = url.absoluteString; + [self logEvent:kAMPDeepLinkOpened withEventProperties:@{ + kAMPEventPropLinkUrl: urlString ?: @"", + }]; +} + #pragma mark - Upload events - (void)uploadEventsWithDelay:(int)delay { @@ -1231,12 +1329,13 @@ - (BOOL)startOrContinueSession:(long long)timestamp { } - (void)startNewSession:(NSNumber *)timestamp { - if (_trackingSessionEvents) { + BOOL loggingSessionEvent = _trackingSessionEvents || self.defaultTracking.sessions; + if (loggingSessionEvent) { [self sendSessionEvent:kAMPSessionEndEvent]; } [self setSessionId:[timestamp longLongValue]]; [self refreshSessionTime:timestamp]; - if (_trackingSessionEvents) { + if (loggingSessionEvent) { [self sendSessionEvent:kAMPSessionStartEvent]; } } @@ -1426,7 +1525,8 @@ - (void)setUserId:(NSString *)userId startNewSession:(BOOL)startNewSession { } [self runOnBackgroundQueue:^{ - if (startNewSession && self->_trackingSessionEvents) { + BOOL loggingSessionEvent = self->_trackingSessionEvents || self.defaultTracking.sessions; + if (startNewSession && loggingSessionEvent) { [self sendSessionEvent:kAMPSessionEndEvent]; } @@ -1442,7 +1542,7 @@ - (void)setUserId:(NSString *)userId startNewSession:(BOOL)startNewSession { NSNumber *timestamp = [NSNumber numberWithLongLong:[[self currentTime] timeIntervalSince1970] * 1000]; [self setSessionId:[timestamp longLongValue]]; [self refreshSessionTime:timestamp]; - if (self->_trackingSessionEvents) { + if (loggingSessionEvent) { [self sendSessionEvent:kAMPSessionStartEvent]; } } diff --git a/Sources/Amplitude/Public/AMPDefaultTrackingOptions.h b/Sources/Amplitude/Public/AMPDefaultTrackingOptions.h new file mode 100644 index 00000000..0b1a63d6 --- /dev/null +++ b/Sources/Amplitude/Public/AMPDefaultTrackingOptions.h @@ -0,0 +1,61 @@ +// +// AMPDefaultTrackingOptions.h +// Copyright (c) 2023 Amplitude Inc. (https://amplitude.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#import + +#ifndef AMPDefaultTrackingOptions_h +#define AMPDefaultTrackingOptions_h + +@interface AMPDefaultTrackingOptions : NSObject + +/** + Enables/disables session tracking. Default to enabled. + */ +@property (nonatomic, assign) BOOL sessions; + +/** + Enables/disables app lifecycle events tracking. Default to disabled. + */ +@property (nonatomic, assign) BOOL appLifecycles; + +/** + Enables/disables deep link events tracking. Default to disabled. + */ +@property (nonatomic, assign) BOOL deepLinks; + +/** + Enables/disables screen view events tracking. Default to disabled. + */ +@property (nonatomic, assign) BOOL screenViews; + +- (instancetype)init; ++ (instancetype)initWithSessions:(BOOL)sessions + appLifecycles:(BOOL)appLifecycles + deepLinks:(BOOL)deepLinks + screenViews:(BOOL)screenViews; ++ (instancetype)initWithAllEnabled; ++ (instancetype)initWithNoneEnabled; + +@end + +#endif /* AMPDefaultTrackingOptions_h */ diff --git a/Sources/Amplitude/Public/Amplitude.h b/Sources/Amplitude/Public/Amplitude.h index 28d42b2a..8fa9ee39 100644 --- a/Sources/Amplitude/Public/Amplitude.h +++ b/Sources/Amplitude/Public/Amplitude.h @@ -29,6 +29,7 @@ #import "AMPIngestionMetadata.h" #import "AMPServerZone.h" #import "AMPMiddleware.h" +#import "AMPDefaultTrackingOptions.h" NS_ASSUME_NONNULL_BEGIN @@ -129,7 +130,16 @@ typedef void (^AMPInitCompletionBlock)(void); /** Whether to automatically log start and end session events corresponding to the start and end of a user's session. */ -@property (nonatomic, assign) BOOL trackingSessionEvents; +@property (nonatomic, assign) BOOL trackingSessionEvents DEPRECATED_MSG_ATTRIBUTE("Use `defaultTracking.sessions` instead"); + +/** + Whether to enable the default events: + - sessions tracking, replacing the previous trackingSessionEvents, including session_start, session_end, default to disabled. + - appLifecycles tracking, including Application Installed, Application Updated, Application Opened, Application Backgrounded, default to disabled. + - deepLinks tracking, including Deep Link Opened, note you will still need to call continueUserActivity or openURL method manually, default to disabled. + - screenViews tracking, including Screen Viewed, default to disabled. + */ +@property (nonatomic, strong) AMPDefaultTrackingOptions *defaultTracking; /** Library name is default as `amplitude-ios`. @@ -758,6 +768,12 @@ typedef void (^AMPInitCompletionBlock)(void); - (NSString *)getContentTypeHeader; +/** + Call to send the Deep Link Opened event, only when defaultTracking.deepLinks is enabled. + */ +- (void)continueUserActivity:(NSUserActivity *)activity NS_SWIFT_NAME(continueUserActivity(activity:)); +- (void)openURL:(NSURL *)url NS_SWIFT_NAME(openURL(url:)); + @end #pragma mark - constants diff --git a/Sources/Amplitude/UIViewController+AMPScreen.h b/Sources/Amplitude/UIViewController+AMPScreen.h new file mode 100644 index 00000000..f8443378 --- /dev/null +++ b/Sources/Amplitude/UIViewController+AMPScreen.h @@ -0,0 +1,34 @@ +// +// UIViewController+AMPScreen.h +// Copyright (c) 2023 Amplitude Inc. (https://amplitude.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#if !TARGET_OS_OSX && !TARGET_OS_WATCH +#import + +@interface UIViewController (AMPScreen) + ++ (void)amp_swizzleViewDidAppear; ++ (UIViewController *)amp_rootViewControllerFromView:(UIView *)view; ++ (UIViewController *)amp_topViewController:(UIViewController *)rootViewController; + +@end +#endif diff --git a/Sources/Amplitude/UIViewController+AMPScreen.m b/Sources/Amplitude/UIViewController+AMPScreen.m new file mode 100644 index 00000000..0fe08562 --- /dev/null +++ b/Sources/Amplitude/UIViewController+AMPScreen.m @@ -0,0 +1,146 @@ +// +// UIViewController+AMPScreen.m +// Copyright (c) 2023 Amplitude Inc. (https://amplitude.com/) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// + +#ifndef AMPLITUDE_DEBUG +#define AMPLITUDE_DEBUG 0 +#endif + +#ifndef AMPLITUDE_LOG +#if AMPLITUDE_DEBUG +# define AMPLITUDE_LOG(fmt, ...) NSLog(fmt, ##__VA_ARGS__) +#else +# define AMPLITUDE_LOG(...) +#endif +#endif + +#import +#import "UIViewController+AMPScreen.h" +#import "Amplitude.h" +#import "AMPConstants.h" + +#if !TARGET_OS_OSX && !TARGET_OS_WATCH + + +@implementation UIViewController (AMPScreen) + ++ (void)amp_swizzleViewDidAppear { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Class class = [self class]; + + SEL originalSelector = @selector(viewDidAppear:); + SEL swizzledSelector = @selector(amp_viewDidAppear:); + + Method originalMethod = class_getInstanceMethod(class, originalSelector); + Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); + + BOOL didAddMethod = + class_addMethod(class, + originalSelector, + method_getImplementation(swizzledMethod), + method_getTypeEncoding(swizzledMethod)); + + if (didAddMethod) { + class_replaceMethod(class, + swizzledSelector, + method_getImplementation(originalMethod), + method_getTypeEncoding(originalMethod)); + } else { + method_exchangeImplementations(originalMethod, swizzledMethod); + } + }); +} + + ++ (UIViewController *)amp_rootViewControllerFromView:(UIView *)view { + UIViewController *root = view.window.rootViewController; + return [self amp_topViewController:root]; +} + ++ (UIViewController *)amp_topViewController:(UIViewController *)rootViewController { + AMPLITUDE_LOG(@"rootViewController is %@", rootViewController); + UIViewController *nextRootViewController = [self amp_nextRootViewController:rootViewController]; + if (nextRootViewController) { + AMPLITUDE_LOG(@"nextRootViewController is %@", nextRootViewController); + return [self amp_topViewController:nextRootViewController]; + } + + return rootViewController; +} + ++ (UIViewController *)amp_nextRootViewController:(UIViewController *)rootViewController { + UIViewController *presentedViewController = rootViewController.presentedViewController; + if (presentedViewController != nil) { + return presentedViewController; + } + + if ([rootViewController isKindOfClass:[UINavigationController class]]) { + UIViewController *lastViewController = ((UINavigationController *)rootViewController).viewControllers.lastObject; + return lastViewController; + } + + if ([rootViewController isKindOfClass:[UITabBarController class]]) { + __auto_type *currentTabViewController = ((UITabBarController*)rootViewController).selectedViewController; + if (currentTabViewController != nil) { + return currentTabViewController; + } + } + + if (rootViewController.childViewControllers.count > 0) { + __auto_type *firstChildViewController = rootViewController.childViewControllers.firstObject; + if (firstChildViewController != nil) { + return firstChildViewController; + } + } + + return nil; +} + +- (void)amp_viewDidAppear:(BOOL)animated { + AMPLITUDE_LOG(@"self is %@", self); + UIViewController *top = [[self class] amp_rootViewControllerFromView:self.view]; + if (!top) { + AMPLITUDE_LOG(@"Failed to infer screen"); + return; + } + + NSString *name = [top title]; + if (!name || name.length == 0) { + // if no class title found, try view controller's description + name = [[[top class] description] stringByReplacingOccurrencesOfString:@"ViewController" withString:@""]; + if (name.length == 0) { + AMPLITUDE_LOG(@"Failed to infer screen name"); + name = @"Unknown"; + } + } + + [[Amplitude instance] logEvent:kAMPScreenViewed withEventProperties:@{ + kAMPEventPropScreenName: name ?: @"", + }]; + + // call original method, this is not recurrsive method call + [self amp_viewDidAppear:animated]; +} + +@end +#endif diff --git a/Tests/Amplitude+Test.h b/Tests/Amplitude+Test.h index 8074778b..db915166 100644 --- a/Tests/Amplitude+Test.h +++ b/Tests/Amplitude+Test.h @@ -25,11 +25,13 @@ - (void)flushUploads:(void (^)(void))handler; - (NSDictionary *)getLastEvent; - (NSDictionary *)getLastEventFromInstanceName:(NSString *)instanceName; +- (NSDictionary *)getLastEventFromInstanceName:(NSString *)instanceName fromEnd:(NSInteger)fromEnd; - (NSDictionary *)getLastIdentify; - (NSDictionary *)getLastInterceptedIdentify; - (NSDictionary *)getEvent:(NSInteger) fromEnd; - (NSDictionary *)getIdentify:(NSInteger) fromEnd; - (NSUInteger)queuedEventCount; +- (NSUInteger)queuedEventCountFromInstanceName:(NSString *)instanceName; - (void)enterForeground; - (void)enterBackground; - (NSDate*)currentTime; diff --git a/Tests/Amplitude+Test.m b/Tests/Amplitude+Test.m index 10fe271e..b7be0982 100644 --- a/Tests/Amplitude+Test.m +++ b/Tests/Amplitude+Test.m @@ -40,6 +40,11 @@ - (NSDictionary *)getLastEventFromInstanceName:(NSString *)instanceName { return [events lastObject]; } +- (NSDictionary *)getLastEventFromInstanceName:(NSString *)instanceName fromEnd:(NSInteger)fromEnd { + NSArray *events = [[AMPDatabaseHelper getDatabaseHelper: instanceName] getEvents:-1 limit:-1]; + return [events objectAtIndex:[events count] - fromEnd - 1]; +} + - (NSDictionary *)getLastEvent { NSArray *events = [[AMPDatabaseHelper getDatabaseHelper] getEvents:-1 limit:-1]; return [events lastObject]; @@ -64,6 +69,10 @@ - (NSUInteger)queuedEventCount { return [[AMPDatabaseHelper getDatabaseHelper] getEventCount]; } +- (NSUInteger)queuedEventCountFromInstanceName:(NSString *)instanceName { + return [[AMPDatabaseHelper getDatabaseHelper: instanceName] getEventCount]; +} + - (void)flushUploads:(void (^)(void))handler { [self performSelector:@selector(uploadEvents)]; [self flushQueue]; diff --git a/Tests/AmplitudeTests.m b/Tests/AmplitudeTests.m index 2e84e85a..af997e1f 100644 --- a/Tests/AmplitudeTests.m +++ b/Tests/AmplitudeTests.m @@ -1549,4 +1549,160 @@ - (void)testRateLimitBackoffLogic { XCTAssertEqual(self.amplitude.eventUploadPeriodSeconds, 20); } +- (void)testInitWithDefaultTrackingOptions { + XCTAssertFalse(self.amplitude.defaultTracking.sessions); + XCTAssertFalse(self.amplitude.defaultTracking.appLifecycles); + XCTAssertFalse(self.amplitude.defaultTracking.screenViews); + XCTAssertFalse(self.amplitude.defaultTracking.deepLinks); +} + +- (void)testSetDefaultTrackingOptions { + Amplitude *client = [Amplitude instanceWithName:@"default_tracking"]; + client.defaultTracking = [AMPDefaultTrackingOptions initWithSessions:NO + appLifecycles:YES + deepLinks:YES + screenViews:YES]; + [client initializeApiKey:@"default_tracking"]; + + XCTAssertFalse(client.defaultTracking.sessions); + XCTAssertTrue(client.defaultTracking.appLifecycles); + XCTAssertTrue(client.defaultTracking.screenViews); + XCTAssertTrue(client.defaultTracking.deepLinks); +} + + +#if !TARGET_OS_OSX && !TARGET_OS_WATCH +- (void)testObserveDidFinishLaunchingNotification { + id mockApplication = [OCMockObject niceMockForClass:[UIApplication class]]; + [[[mockApplication stub] andReturn:mockApplication] sharedApplication]; + OCMStub([mockApplication applicationState]).andReturn(UIApplicationStateInactive); + + NSString *instanceName = @"default_tracking_ObserveDidFinishLaunchingNotification"; + Amplitude *client = [Amplitude instanceWithName:instanceName]; + client.defaultTracking.appLifecycles = YES; + [client initializeApiKey:@"default_tracking"]; + + UIApplication *app = [AMPUtils getSharedApplication]; + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center postNotificationName:UIApplicationDidFinishLaunchingNotification object:app]; + + AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper:instanceName]; + [dbHelper insertOrReplaceKeyValue:@"app_build" value:nil]; + [dbHelper insertOrReplaceKeyValue:@"app_version" value:nil]; + + [client flushQueue]; + + NSDictionary *event1 = [client getLastEventFromInstanceName:instanceName fromEnd: 1]; + XCTAssertEqualObjects([event1 objectForKey:@"event_type"], kAMPApplicationInstalled); + + NSDictionary *event2 = [client getLastEventFromInstanceName:instanceName fromEnd: 0]; + XCTAssertEqualObjects([event2 objectForKey:@"event_type"], kAMPApplicationOpened); + XCTAssertEqualObjects([[event2 objectForKey:@"event_properties"] objectForKey:kAMPEventPropFromBackground], @NO); +} + +- (void)testObserveDidFinishLaunchingNotificationWithPreviousBuild { + id mockApplication = [OCMockObject niceMockForClass:[UIApplication class]]; + [[[mockApplication stub] andReturn:mockApplication] sharedApplication]; + OCMStub([mockApplication applicationState]).andReturn(UIApplicationStateInactive); + + NSString *instanceName = @"default_tracking_ObserveDidFinishLaunchingNotificationWithPreviousBuild"; + Amplitude *client = [Amplitude instanceWithName:instanceName]; + client.defaultTracking.appLifecycles = YES; + [client initializeApiKey:@"default_tracking"]; + + UIApplication *app = [AMPUtils getSharedApplication]; + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center postNotificationName:UIApplicationDidFinishLaunchingNotification object:app]; + + AMPDatabaseHelper *dbHelper = [AMPDatabaseHelper getDatabaseHelper:instanceName]; + [dbHelper insertOrReplaceKeyValue:@"app_build" value:@"test"]; + [dbHelper insertOrReplaceKeyValue:@"app_version" value:@"test"]; + + [client flushQueue]; + + // This check is not ideal, to avoid flaky test, we only check the first event prefix. + NSDictionary *event1 = [client getLastEventFromInstanceName:instanceName fromEnd: 1]; + XCTAssertTrue([[event1 objectForKey:@"event_type"] hasPrefix:@"[Amplitude] Application"]); + + NSDictionary *event2 = [client getLastEventFromInstanceName:instanceName fromEnd: 0]; + XCTAssertEqualObjects([event2 objectForKey:@"event_type"], kAMPApplicationOpened); + XCTAssertEqualObjects([[event2 objectForKey:@"event_properties"] objectForKey:kAMPEventPropFromBackground], @NO); +} + +- (void)testObserveWillEnterForegroundNotification { + id mockApplication = [OCMockObject niceMockForClass:[UIApplication class]]; + [[[mockApplication stub] andReturn:mockApplication] sharedApplication]; + OCMStub([mockApplication applicationState]).andReturn(UIApplicationStateInactive); + + NSString *instanceName = @"default_tracking_ObserveWillEnterForegroundNotification"; + Amplitude *client = [Amplitude instanceWithName:instanceName]; + client.defaultTracking.appLifecycles = YES; + [client initializeApiKey:@"default_tracking"]; + + UIApplication *app = [AMPUtils getSharedApplication]; + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center postNotificationName:UIApplicationWillEnterForegroundNotification object:app]; + + [client flushQueue]; + + NSDictionary *event2 = [client getLastEventFromInstanceName:instanceName fromEnd: 0]; + XCTAssertEqualObjects([event2 objectForKey:@"event_type"], kAMPApplicationOpened); + XCTAssertEqualObjects([[event2 objectForKey:@"event_properties"] objectForKey:kAMPEventPropFromBackground], @YES); +} + +- (void)testObserveDidEnterBackgroundNotification { + id mockApplication = [OCMockObject niceMockForClass:[UIApplication class]]; + [[[mockApplication stub] andReturn:mockApplication] sharedApplication]; + OCMStub([mockApplication applicationState]).andReturn(UIApplicationStateInactive); + + NSString *instanceName = @"default_tracking_ObserveDidEnterBackgroundNotification"; + Amplitude *client = [Amplitude instanceWithName:instanceName]; + client.defaultTracking.appLifecycles = YES; + [client initializeApiKey:@"default_tracking"]; + + UIApplication *app = [AMPUtils getSharedApplication]; + NSNotificationCenter *center = [NSNotificationCenter defaultCenter]; + [center postNotificationName:UIApplicationDidEnterBackgroundNotification object:app]; + + [client flushQueue]; + + NSDictionary *event2 = [client getLastEventFromInstanceName:instanceName fromEnd: 0]; + XCTAssertEqualObjects([event2 objectForKey:@"event_type"], kAMPApplicationBackgrounded); +} + +#endif + +- (void)testContinueUserActivityFiresDeepLinkEvent { + NSString *instanceName = @"default_tracking_UserActivityFiresDeepLinkEvent"; + Amplitude *client = [Amplitude instanceWithName:instanceName]; + client.defaultTracking.deepLinks = YES; + [client initializeApiKey:@"default_tracking"]; + + NSUserActivity *userActivity = [[NSUserActivity alloc] initWithActivityType:NSUserActivityTypeBrowsingWeb]; + [userActivity setWebpageURL:[NSURL URLWithString:@"https://test-app.com"]]; + [client continueUserActivity:userActivity]; + + [client flushQueue]; + + NSDictionary *event = [client getLastEventFromInstanceName:instanceName]; + XCTAssertEqualObjects([event objectForKey:@"event_type"], kAMPDeepLinkOpened); + XCTAssertEqualObjects([[event objectForKey:@"event_properties"] objectForKey:kAMPEventPropLinkUrl], @"https://test-app.com"); +} + +- (void)testOpenURLFiresDeepLinkEvent { + NSString *instanceName = @"default_tracking_OpenURLFiresDeepLinkEvent"; + Amplitude *client = [Amplitude instanceWithName:instanceName]; + client.defaultTracking.deepLinks = YES; + [client initializeApiKey:@"default_tracking"]; + + NSURL *url = [NSURL URLWithString:@"https://test-app.com"]; + [client openURL:url]; + + [client flushQueue]; + + NSDictionary *event = [client getLastEventFromInstanceName:instanceName]; + XCTAssertEqualObjects([event objectForKey:@"event_type"], kAMPDeepLinkOpened); + XCTAssertEqualObjects([[event objectForKey:@"event_properties"] objectForKey:kAMPEventPropLinkUrl], @"https://test-app.com"); +} + @end diff --git a/Tests/DefaultTrackingOptionsTests.m b/Tests/DefaultTrackingOptionsTests.m new file mode 100644 index 00000000..a3d5f466 --- /dev/null +++ b/Tests/DefaultTrackingOptionsTests.m @@ -0,0 +1,58 @@ +// +// DefaultTrackingOptionsTests.m +// Amplitude +// +// Created by Marvin Liu on 6/16/23. +// Copyright © 2023 Amplitude. All rights reserved. +// + +#import +#import "AMPDefaultTrackingOptions.h" +#import "AMPConstants.h" + +@interface DefaultTrackingOptionsTests : XCTestCase + +@end + +@implementation DefaultTrackingOptionsTests + +- (void)testInit { + AMPDefaultTrackingOptions *instance = [[AMPDefaultTrackingOptions alloc] init]; + + XCTAssertFalse(instance.sessions); + XCTAssertFalse(instance.appLifecycles); + XCTAssertFalse(instance.deepLinks); + XCTAssertFalse(instance.screenViews); +} + +- (void)testInitWithCustomeOptions { + AMPDefaultTrackingOptions *instance = [AMPDefaultTrackingOptions initWithSessions:NO + appLifecycles:YES + deepLinks:YES + screenViews:YES]; + + XCTAssertFalse(instance.sessions); + XCTAssertTrue(instance.appLifecycles); + XCTAssertTrue(instance.deepLinks); + XCTAssertTrue(instance.screenViews); +} + +- (void)testInitWithAllEnabled { + AMPDefaultTrackingOptions *instance = [AMPDefaultTrackingOptions initWithAllEnabled]; + + XCTAssertTrue(instance.sessions); + XCTAssertTrue(instance.appLifecycles); + XCTAssertTrue(instance.deepLinks); + XCTAssertTrue(instance.screenViews); +} + +- (void)testInitWithNoneEnabled { + AMPDefaultTrackingOptions *instance = [AMPDefaultTrackingOptions initWithNoneEnabled]; + + XCTAssertFalse(instance.sessions); + XCTAssertFalse(instance.appLifecycles); + XCTAssertFalse(instance.deepLinks); + XCTAssertFalse(instance.screenViews); +} + +@end diff --git a/Tests/ScreenViewTests.m b/Tests/ScreenViewTests.m new file mode 100644 index 00000000..2d040c2d --- /dev/null +++ b/Tests/ScreenViewTests.m @@ -0,0 +1,87 @@ +// +// ScreenViewTests.m +// Amplitude +// +// Created by Marvin Liu on 6/28/23. +// Copyright © 2023 Amplitude. All rights reserved. +// + +#import +#import "Amplitude.h" +#import "Amplitude+Test.h" +#import "BaseTestCase.h" +#import "UIViewController+AMPScreen.h" + +#if !TARGET_OS_OSX && !TARGET_OS_WATCH +@interface ScreenViewTests : BaseTestCase + +@end + +@implementation ScreenViewTests { + UIWindow *window; + UIViewController *rootViewController; +} + +- (void)setUp { + [super setUp]; + Amplitude *amplitude = [Amplitude instance]; + amplitude.defaultTracking.screenViews = YES; + [amplitude initializeApiKey:@"test-api-key"]; + + window = [[UIWindow alloc] init]; + rootViewController = [[UIViewController alloc] init]; + [window addSubview:rootViewController.view]; +} + +- (void)tearDown { + [super tearDown]; +} + +- (void)testTopViewControllerReturnsRootController { + UIViewController *controller = [UIViewController amp_topViewController:rootViewController]; + + XCTAssertEqual(controller, rootViewController); +} + +- (void)testTopViewControllerReturnsPresentedController { + UIViewController *presentController = [[UIViewController alloc] init]; + [rootViewController presentViewController:presentController animated:NO completion:^{}]; + + XCTAssertEqual(presentController, [UIViewController amp_topViewController:rootViewController]); +} + +- (void)testTopViewControllerReturnsNavigationPushedController { + UINavigationController *navigationController = [[UINavigationController alloc] init]; + [rootViewController presentViewController:navigationController animated:NO completion:^{}]; + + UIViewController *controller = [[UIViewController alloc] init]; + [navigationController pushViewController:controller animated:NO]; + + XCTAssertEqual(controller, [UIViewController amp_topViewController:rootViewController]); +} + +- (void)testTopViewControllerReturnsSelectedTabBarController { + UITabBarController *tabBarController = [[UITabBarController alloc] init]; + [rootViewController presentViewController:tabBarController animated:NO completion:^{}]; + + UIViewController *controller = [[UIViewController alloc] init]; + [tabBarController setViewControllers:@[[[UIViewController alloc] init], controller]]; + [tabBarController setSelectedIndex:1]; + + XCTAssertEqual(controller, [UIViewController amp_topViewController:rootViewController]); +} + +- (void)testTopViewControllerReturnsFirstChildViewController { + UIViewController *containerController = [[UIViewController alloc] init]; + [rootViewController presentViewController:containerController animated:NO completion:^{}]; + + + UIViewController *controller = [[UIViewController alloc] init]; + [containerController addChildViewController:controller]; + [containerController addChildViewController:[[UIViewController alloc] init]]; + + XCTAssertEqual(controller, [UIViewController amp_topViewController:rootViewController]); +} + +@end +#endif diff --git a/Tests/SessionTests.m b/Tests/SessionTests.m index 1a4d6a01..ac8f4329 100644 --- a/Tests/SessionTests.m +++ b/Tests/SessionTests.m @@ -184,7 +184,9 @@ - (void)testTrackSessionEvents { id mockAmplitude = [OCMockObject partialMockForObject:self.amplitude]; NSDate *date = [NSDate dateWithTimeIntervalSince1970:1000]; [(Amplitude *)[[mockAmplitude expect] andReturnValue:OCMOCK_VALUE(date)] currentTime]; - [mockAmplitude setTrackingSessionEvents:YES]; + AMPDefaultTrackingOptions *defaultTracking = [AMPDefaultTrackingOptions initWithNoneEnabled]; + defaultTracking.sessions = YES; + [mockAmplitude setDefaultTracking:defaultTracking]; [mockAmplitude initializeApiKey:apiKey userId:nil]; [mockAmplitude flushQueueWithQueue:[mockAmplitude initializerQueue]]; @@ -245,7 +247,9 @@ - (void)testSessionEventsOn32BitDevices { id mockAmplitude = [OCMockObject partialMockForObject:self.amplitude]; NSDate *date = [NSDate dateWithTimeIntervalSince1970:21474836470]; [(Amplitude *)[[mockAmplitude expect] andReturnValue:OCMOCK_VALUE(date)] currentTime]; - [mockAmplitude setTrackingSessionEvents:YES]; + AMPDefaultTrackingOptions *defaultTracking = [AMPDefaultTrackingOptions initWithNoneEnabled]; + defaultTracking.sessions = YES; + [mockAmplitude setDefaultTracking:defaultTracking]; [mockAmplitude initializeApiKey:apiKey userId:nil]; [mockAmplitude flushQueueWithQueue:[mockAmplitude initializerQueue]]; @@ -285,7 +289,9 @@ - (void)testSkipSessionCheckWhenLoggingSessionEvents { NSNumber *timestamp = [NSNumber numberWithLongLong:[date timeIntervalSince1970] * 1000]; [dbHelper insertOrReplaceKeyLongValue:@"previous_session_id" value:timestamp]; - self.amplitude.trackingSessionEvents = YES; + AMPDefaultTrackingOptions *defaultTracking = [AMPDefaultTrackingOptions initWithNoneEnabled]; + defaultTracking.sessions = YES; + [self.amplitude setDefaultTracking:defaultTracking]; [self.amplitude initializeApiKey:apiKey userId:nil]; [self.amplitude flushQueue];