diff --git a/Adjust.podspec b/Adjust.podspec index 6f3b34f5c..e64e74d22 100644 --- a/Adjust.podspec +++ b/Adjust.podspec @@ -1,11 +1,11 @@ Pod::Spec.new do |s| s.name = "Adjust" - s.version = "4.6.0" + s.version = "4.7.0" s.summary = "This is the iOS SDK of adjust. You can read more about it at http://adjust.com." s.homepage = "http://adjust.com" s.license = { :type => 'MIT', :file => 'MIT-LICENSE' } s.author = { "Christian Wellenbrock" => "welle@adjust.com" } - s.source = { :git => "https://github.com/adjust/ios_sdk.git", :tag => "v4.6.0" } + s.source = { :git => "https://github.com/adjust/ios_sdk.git", :tag => "v4.7.0" } s.ios.deployment_target = '6.0' s.tvos.deployment_target = '9.0' s.framework = 'SystemConfiguration' diff --git a/Adjust.xcodeproj/project.pbxproj b/Adjust.xcodeproj/project.pbxproj index 20d223c07..807da1ac4 100644 --- a/Adjust.xcodeproj/project.pbxproj +++ b/Adjust.xcodeproj/project.pbxproj @@ -30,6 +30,14 @@ 9601CAE91C74BAAE00670879 /* ADJEventFailure.m in Sources */ = {isa = PBXBuildFile; fileRef = 9601CAE71C74BAAE00670879 /* ADJEventFailure.m */; }; 9609BC6A19EEA55800E02303 /* ADJEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 9609BC6919EEA55800E02303 /* ADJEvent.m */; }; 960A8BB91A029A8000F2BB95 /* ADJConfig.m in Sources */ = {isa = PBXBuildFile; fileRef = 960A8BB81A029A8000F2BB95 /* ADJConfig.m */; }; + 96164D721CC8FA73009431AB /* ADJSdkClickHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 96164D701CC8FA73009431AB /* ADJSdkClickHandler.h */; }; + 96164D731CC8FA73009431AB /* ADJSdkClickHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 96164D711CC8FA73009431AB /* ADJSdkClickHandler.m */; }; + 96164D841CCA4D10009431AB /* ADJSdkClickHandler.h in Headers */ = {isa = PBXBuildFile; fileRef = 96164D701CC8FA73009431AB /* ADJSdkClickHandler.h */; }; + 96164D861CCA4D27009431AB /* ADJBackoffStrategy.h in Headers */ = {isa = PBXBuildFile; fileRef = 965B7F2E1CC78F6600098639 /* ADJBackoffStrategy.h */; }; + 96164D871CCA4D36009431AB /* ADJSdkClickHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 96164D711CC8FA73009431AB /* ADJSdkClickHandler.m */; }; + 96164D891CCA4D48009431AB /* ADJBackoffStrategy.m in Sources */ = {isa = PBXBuildFile; fileRef = 965B7F2F1CC78F6600098639 /* ADJBackoffStrategy.m */; }; + 96164D8C1CCA4DEA009431AB /* ADJSystemProfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 96164D8A1CCA4DEA009431AB /* ADJSystemProfile.h */; }; + 96164D8D1CCA4DEA009431AB /* ADJSystemProfile.m in Sources */ = {isa = PBXBuildFile; fileRef = 96164D8B1CCA4DEA009431AB /* ADJSystemProfile.m */; }; 9620EA451B062D6F009673F1 /* ADJTestActivityPackage.m in Sources */ = {isa = PBXBuildFile; fileRef = 9620EA441B062D6F009673F1 /* ADJTestActivityPackage.m */; }; 9620EA4E1B06401B009673F1 /* ADJTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 9620EA4D1B06401B009673F1 /* ADJTest.m */; }; 9620EA511B065177009673F1 /* ADJPackageFields.m in Sources */ = {isa = PBXBuildFile; fileRef = 9620EA501B065177009673F1 /* ADJPackageFields.m */; }; @@ -39,9 +47,12 @@ 96325E8C190E8D6B00A97911 /* AdSupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 96325E84190E5CD900A97911 /* AdSupport.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 965307F61A000DA400107FF9 /* ADJDeviceInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 965307F51A000DA400107FF9 /* ADJDeviceInfo.m */; }; 9657BF6F1A3B029000CD6853 /* ADJAttributionHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9657BF6E1A3B029000CD6853 /* ADJAttributionHandlerTests.m */; }; + 965B7F301CC78F6600098639 /* ADJBackoffStrategy.h in Headers */ = {isa = PBXBuildFile; fileRef = 965B7F2E1CC78F6600098639 /* ADJBackoffStrategy.h */; }; + 965B7F311CC78F6600098639 /* ADJBackoffStrategy.m in Sources */ = {isa = PBXBuildFile; fileRef = 965B7F2F1CC78F6600098639 /* ADJBackoffStrategy.m */; }; 9679921118BBAE2800394606 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9679921018BBAE2800394606 /* Foundation.framework */; }; 9679922018BBAE2800394606 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9679921018BBAE2800394606 /* Foundation.framework */; }; 9679922518BBAE2800394606 /* libAdjust.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9679920D18BBAE2800394606 /* libAdjust.a */; }; + 967D069A1CE2272500ECFCE3 /* ADJSessionState.m in Sources */ = {isa = PBXBuildFile; fileRef = 967D06991CE2272500ECFCE3 /* ADJSessionState.m */; }; 968173831C3C2D07002AE1DE /* ADJSessionSuccess.h in Headers */ = {isa = PBXBuildFile; fileRef = 968173811C3C2D07002AE1DE /* ADJSessionSuccess.h */; settings = {ATTRIBUTES = (Public, ); }; }; 968173841C3C2D07002AE1DE /* ADJSessionSuccess.m in Sources */ = {isa = PBXBuildFile; fileRef = 968173821C3C2D07002AE1DE /* ADJSessionSuccess.m */; }; 968173871C3C2D36002AE1DE /* ADJSessionFailure.h in Headers */ = {isa = PBXBuildFile; fileRef = 968173851C3C2D36002AE1DE /* ADJSessionFailure.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -52,6 +63,8 @@ 96854A601B1F278C002B2874 /* ADJTimerCycle.m in Sources */ = {isa = PBXBuildFile; fileRef = 96854A5E1B1F278C002B2874 /* ADJTimerCycle.m */; }; 969952CF1A012F5300928462 /* ADJAttributionHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 969952CE1A012F5300928462 /* ADJAttributionHandler.m */; }; 969952D21A01309200928462 /* ADJAttribution.m in Sources */ = {isa = PBXBuildFile; fileRef = 969952D11A01309200928462 /* ADJAttribution.m */; }; + 96A2D2261CEE0E6200C39DE0 /* ADJSdkClickHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 96A2D2251CEE0E6200C39DE0 /* ADJSdkClickHandlerTests.m */; }; + 96A2D2381CEF3B2D00C39DE0 /* ADJRequestHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 96A2D2371CEF3B2D00C39DE0 /* ADJRequestHandlerTests.m */; }; 96BCFBCD1AC99231005A65C5 /* Adjust.h in Headers */ = {isa = PBXBuildFile; fileRef = 96E5E34C18BBB48A008E7B30 /* Adjust.h */; settings = {ATTRIBUTES = (Public, ); }; }; 96BCFBCE1AC99235005A65C5 /* ADJEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = 9609BC6819EEA55800E02303 /* ADJEvent.h */; settings = {ATTRIBUTES = (Public, ); }; }; 96BCFBD01AC9923F005A65C5 /* ADJConfig.h in Headers */ = {isa = PBXBuildFile; fileRef = 960A8BB71A029A8000F2BB95 /* ADJConfig.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -76,8 +89,6 @@ 96C93DF61AC47FE000B53F56 /* NSData+ADJAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 96C93DF41AC47F2E00B53F56 /* NSData+ADJAdditions.m */; }; 96CD2BE01A13BFC600A40AFB /* NSString+ADJAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 96CD2BDD1A13BFC600A40AFB /* NSString+ADJAdditions.m */; }; 96CD2BE11A13BFC600A40AFB /* UIDevice+ADJAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 96CD2BDF1A13BFC600A40AFB /* UIDevice+ADJAdditions.m */; }; - 96DD62751C52810500DDF56A /* ADJTrackingSucceededDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 96DD62741C52810500DDF56A /* ADJTrackingSucceededDelegate.m */; }; - 96DD627A1C52818000DDF56A /* ADJTrackingFailedDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 96DD62791C52818000DDF56A /* ADJTrackingFailedDelegate.m */; }; 96E5E38118BBB48A008E7B30 /* Adjust.m in Sources */ = {isa = PBXBuildFile; fileRef = 96E5E34D18BBB48A008E7B30 /* Adjust.m */; }; 96E5E38B18BBB48A008E7B30 /* ADJActivityHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 96E5E36318BBB48A008E7B30 /* ADJActivityHandler.m */; }; 96E5E38C18BBB48A008E7B30 /* ADJActivityKind.m in Sources */ = {isa = PBXBuildFile; fileRef = 96E5E36518BBB48A008E7B30 /* ADJActivityKind.m */; }; @@ -95,12 +106,16 @@ 96E5E3B218BBB49E008E7B30 /* ADJPackageHandlerMock.m in Sources */ = {isa = PBXBuildFile; fileRef = 96E5E3A318BBB49E008E7B30 /* ADJPackageHandlerMock.m */; }; 96E5E3B318BBB49E008E7B30 /* ADJPackageHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 96E5E3A418BBB49E008E7B30 /* ADJPackageHandlerTests.m */; }; 96E5E3B418BBB49E008E7B30 /* ADJRequestHandlerMock.m in Sources */ = {isa = PBXBuildFile; fileRef = 96E5E3A618BBB49E008E7B30 /* ADJRequestHandlerMock.m */; }; - 96E5E3B518BBB49E008E7B30 /* AIRequestHandlerTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 96E5E3A718BBB49E008E7B30 /* AIRequestHandlerTests.m */; }; 96E5E3B618BBB49E008E7B30 /* ADJTestsUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 96E5E3A918BBB49E008E7B30 /* ADJTestsUtil.m */; }; 96E5E3B718BBB49E008E7B30 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = 96E5E3AA18BBB49E008E7B30 /* InfoPlist.strings */; }; 96E5E3B818BBB49E008E7B30 /* NSURLConnection+NSURLConnectionSynchronousLoadingMocking.m in Sources */ = {isa = PBXBuildFile; fileRef = 96E5E3AD18BBB49E008E7B30 /* NSURLConnection+NSURLConnectionSynchronousLoadingMocking.m */; }; + 96E768A11CE6267D005D811C /* ADJDeeplinkDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 96E768A01CE6267D005D811C /* ADJDeeplinkDelegate.m */; }; + 96E768AA1CE6281F005D811C /* ADJTrackingDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 96E768A91CE6281F005D811C /* ADJTrackingDelegate.m */; }; 96ED00391A38A4CD00209110 /* ADJAttributionHandlerMock.m in Sources */ = {isa = PBXBuildFile; fileRef = 96ED00381A38A4CD00209110 /* ADJAttributionHandlerMock.m */; }; - 96ED003E1A38A98C00209110 /* ADJDelegateTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 96ED003D1A38A98C00209110 /* ADJDelegateTest.m */; }; + 96ED003E1A38A98C00209110 /* ADJAttributionChangedDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 96ED003D1A38A98C00209110 /* ADJAttributionChangedDelegate.m */; }; + 96ED74FB1CD8085000C073B8 /* ADJSystemProfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 96ED74F91CD8085000C073B8 /* ADJSystemProfile.h */; }; + 96ED74FC1CD8085000C073B8 /* ADJSystemProfile.m in Sources */ = {isa = PBXBuildFile; fileRef = 96ED74FA1CD8085000C073B8 /* ADJSystemProfile.m */; }; + 96ED74FF1CD81B3E00C073B8 /* ADJSdkClickHandlerMock.m in Sources */ = {isa = PBXBuildFile; fileRef = 96ED74FE1CD81B3E00C073B8 /* ADJSdkClickHandlerMock.m */; }; 96FCC53A1C186426007BBFE1 /* ADJResponseData.h in Headers */ = {isa = PBXBuildFile; fileRef = 96FCC5361C186426007BBFE1 /* ADJResponseData.h */; }; 96FCC53B1C186426007BBFE1 /* ADJResponseData.m in Sources */ = {isa = PBXBuildFile; fileRef = 96FCC5371C186426007BBFE1 /* ADJResponseData.m */; }; 96FDD75E1C566C38009C402E /* NSURLSession+NSURLDataWithRequestMocking.m in Sources */ = {isa = PBXBuildFile; fileRef = 96FDD75D1C566C38009C402E /* NSURLSession+NSURLDataWithRequestMocking.m */; }; @@ -245,6 +260,10 @@ 9609BC6919EEA55800E02303 /* ADJEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJEvent.m; sourceTree = ""; }; 960A8BB71A029A8000F2BB95 /* ADJConfig.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJConfig.h; sourceTree = ""; }; 960A8BB81A029A8000F2BB95 /* ADJConfig.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJConfig.m; sourceTree = ""; }; + 96164D701CC8FA73009431AB /* ADJSdkClickHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJSdkClickHandler.h; sourceTree = ""; }; + 96164D711CC8FA73009431AB /* ADJSdkClickHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJSdkClickHandler.m; sourceTree = ""; }; + 96164D8A1CCA4DEA009431AB /* ADJSystemProfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJSystemProfile.h; sourceTree = ""; }; + 96164D8B1CCA4DEA009431AB /* ADJSystemProfile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJSystemProfile.m; sourceTree = ""; }; 9620EA431B062D6F009673F1 /* ADJTestActivityPackage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJTestActivityPackage.h; sourceTree = ""; }; 9620EA441B062D6F009673F1 /* ADJTestActivityPackage.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJTestActivityPackage.m; sourceTree = ""; }; 9620EA4C1B06401B009673F1 /* ADJTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJTest.h; sourceTree = ""; }; @@ -258,9 +277,13 @@ 9644B7EA19F148F3008576FC /* ADJDeviceInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJDeviceInfo.h; sourceTree = ""; }; 965307F51A000DA400107FF9 /* ADJDeviceInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJDeviceInfo.m; sourceTree = ""; }; 9657BF6E1A3B029000CD6853 /* ADJAttributionHandlerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJAttributionHandlerTests.m; sourceTree = ""; }; + 965B7F2E1CC78F6600098639 /* ADJBackoffStrategy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJBackoffStrategy.h; sourceTree = ""; }; + 965B7F2F1CC78F6600098639 /* ADJBackoffStrategy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJBackoffStrategy.m; sourceTree = ""; }; 9679920D18BBAE2800394606 /* libAdjust.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libAdjust.a; sourceTree = BUILT_PRODUCTS_DIR; }; 9679921018BBAE2800394606 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 9679921D18BBAE2800394606 /* AdjustTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AdjustTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 967D06981CE2272500ECFCE3 /* ADJSessionState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJSessionState.h; sourceTree = ""; }; + 967D06991CE2272500ECFCE3 /* ADJSessionState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJSessionState.m; sourceTree = ""; }; 968173811C3C2D07002AE1DE /* ADJSessionSuccess.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJSessionSuccess.h; sourceTree = ""; }; 968173821C3C2D07002AE1DE /* ADJSessionSuccess.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJSessionSuccess.m; sourceTree = ""; }; 968173851C3C2D36002AE1DE /* ADJSessionFailure.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJSessionFailure.h; sourceTree = ""; }; @@ -273,16 +296,14 @@ 969952CE1A012F5300928462 /* ADJAttributionHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJAttributionHandler.m; sourceTree = ""; }; 969952D01A01309200928462 /* ADJAttribution.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJAttribution.h; sourceTree = ""; }; 969952D11A01309200928462 /* ADJAttribution.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJAttribution.m; sourceTree = ""; }; + 96A2D2251CEE0E6200C39DE0 /* ADJSdkClickHandlerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJSdkClickHandlerTests.m; sourceTree = ""; }; + 96A2D2371CEF3B2D00C39DE0 /* ADJRequestHandlerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJRequestHandlerTests.m; sourceTree = ""; }; 96C93DF31AC47F2E00B53F56 /* NSData+ADJAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+ADJAdditions.h"; sourceTree = ""; }; 96C93DF41AC47F2E00B53F56 /* NSData+ADJAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+ADJAdditions.m"; sourceTree = ""; }; 96CD2BDC1A13BFC600A40AFB /* NSString+ADJAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+ADJAdditions.h"; sourceTree = ""; }; 96CD2BDD1A13BFC600A40AFB /* NSString+ADJAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+ADJAdditions.m"; sourceTree = ""; }; 96CD2BDE1A13BFC600A40AFB /* UIDevice+ADJAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIDevice+ADJAdditions.h"; sourceTree = ""; }; 96CD2BDF1A13BFC600A40AFB /* UIDevice+ADJAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIDevice+ADJAdditions.m"; sourceTree = ""; }; - 96DD62731C52810500DDF56A /* ADJTrackingSucceededDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJTrackingSucceededDelegate.h; sourceTree = ""; }; - 96DD62741C52810500DDF56A /* ADJTrackingSucceededDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJTrackingSucceededDelegate.m; sourceTree = ""; }; - 96DD62781C52818000DDF56A /* ADJTrackingFailedDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJTrackingFailedDelegate.h; sourceTree = ""; }; - 96DD62791C52818000DDF56A /* ADJTrackingFailedDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJTrackingFailedDelegate.m; sourceTree = ""; }; 96E5E34C18BBB48A008E7B30 /* Adjust.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Adjust.h; sourceTree = ""; }; 96E5E34D18BBB48A008E7B30 /* Adjust.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Adjust.m; sourceTree = ""; }; 96E5E36218BBB48A008E7B30 /* ADJActivityHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJActivityHandler.h; sourceTree = ""; }; @@ -317,16 +338,23 @@ 96E5E3A418BBB49E008E7B30 /* ADJPackageHandlerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJPackageHandlerTests.m; sourceTree = ""; }; 96E5E3A518BBB49E008E7B30 /* ADJRequestHandlerMock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJRequestHandlerMock.h; sourceTree = ""; }; 96E5E3A618BBB49E008E7B30 /* ADJRequestHandlerMock.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJRequestHandlerMock.m; sourceTree = ""; }; - 96E5E3A718BBB49E008E7B30 /* AIRequestHandlerTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AIRequestHandlerTests.m; sourceTree = ""; }; 96E5E3A818BBB49E008E7B30 /* ADJTestsUtil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJTestsUtil.h; sourceTree = ""; }; 96E5E3A918BBB49E008E7B30 /* ADJTestsUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJTestsUtil.m; sourceTree = ""; }; 96E5E3AB18BBB49E008E7B30 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; 96E5E3AC18BBB49E008E7B30 /* NSURLConnection+NSURLConnectionSynchronousLoadingMocking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURLConnection+NSURLConnectionSynchronousLoadingMocking.h"; sourceTree = ""; }; 96E5E3AD18BBB49E008E7B30 /* NSURLConnection+NSURLConnectionSynchronousLoadingMocking.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSURLConnection+NSURLConnectionSynchronousLoadingMocking.m"; sourceTree = ""; }; + 96E7689F1CE6267D005D811C /* ADJDeeplinkDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJDeeplinkDelegate.h; sourceTree = ""; }; + 96E768A01CE6267D005D811C /* ADJDeeplinkDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJDeeplinkDelegate.m; sourceTree = ""; }; + 96E768A81CE6281F005D811C /* ADJTrackingDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJTrackingDelegate.h; sourceTree = ""; }; + 96E768A91CE6281F005D811C /* ADJTrackingDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJTrackingDelegate.m; sourceTree = ""; }; 96ED00371A38A4CD00209110 /* ADJAttributionHandlerMock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJAttributionHandlerMock.h; sourceTree = ""; }; 96ED00381A38A4CD00209110 /* ADJAttributionHandlerMock.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJAttributionHandlerMock.m; sourceTree = ""; }; - 96ED003C1A38A98C00209110 /* ADJDelegateTest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJDelegateTest.h; sourceTree = ""; }; - 96ED003D1A38A98C00209110 /* ADJDelegateTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJDelegateTest.m; sourceTree = ""; }; + 96ED003C1A38A98C00209110 /* ADJAttributionChangedDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJAttributionChangedDelegate.h; sourceTree = ""; }; + 96ED003D1A38A98C00209110 /* ADJAttributionChangedDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJAttributionChangedDelegate.m; sourceTree = ""; }; + 96ED74F91CD8085000C073B8 /* ADJSystemProfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJSystemProfile.h; sourceTree = ""; }; + 96ED74FA1CD8085000C073B8 /* ADJSystemProfile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJSystemProfile.m; sourceTree = ""; }; + 96ED74FD1CD81B3E00C073B8 /* ADJSdkClickHandlerMock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJSdkClickHandlerMock.h; sourceTree = ""; }; + 96ED74FE1CD81B3E00C073B8 /* ADJSdkClickHandlerMock.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJSdkClickHandlerMock.m; sourceTree = ""; }; 96FCC5361C186426007BBFE1 /* ADJResponseData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJResponseData.h; sourceTree = ""; }; 96FCC5371C186426007BBFE1 /* ADJResponseData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJResponseData.m; sourceTree = ""; }; 96FDD75C1C566C38009C402E /* NSURLSession+NSURLDataWithRequestMocking.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSURLSession+NSURLDataWithRequestMocking.h"; sourceTree = ""; }; @@ -497,6 +525,8 @@ 96E5E34B18BBB48A008E7B30 /* Adjust */ = { isa = PBXGroup; children = ( + 96164D8A1CCA4DEA009431AB /* ADJSystemProfile.h */, + 96164D8B1CCA4DEA009431AB /* ADJSystemProfile.m */, 96CD2BDB1A13BFC600A40AFB /* ADJAdditions */, 96E5E34C18BBB48A008E7B30 /* Adjust.h */, 96E5E34D18BBB48A008E7B30 /* Adjust.m */, @@ -544,6 +574,10 @@ 9601CAE31C74B70600670879 /* ADJEventSuccess.m */, 9601CAE61C74BAAE00670879 /* ADJEventFailure.h */, 9601CAE71C74BAAE00670879 /* ADJEventFailure.m */, + 965B7F2E1CC78F6600098639 /* ADJBackoffStrategy.h */, + 965B7F2F1CC78F6600098639 /* ADJBackoffStrategy.m */, + 96164D701CC8FA73009431AB /* ADJSdkClickHandler.h */, + 96164D711CC8FA73009431AB /* ADJSdkClickHandler.m */, ); path = Adjust; sourceTree = ""; @@ -563,7 +597,7 @@ 96E5E3A418BBB49E008E7B30 /* ADJPackageHandlerTests.m */, 96E5E3A518BBB49E008E7B30 /* ADJRequestHandlerMock.h */, 96E5E3A618BBB49E008E7B30 /* ADJRequestHandlerMock.m */, - 96E5E3A718BBB49E008E7B30 /* AIRequestHandlerTests.m */, + 96A2D2371CEF3B2D00C39DE0 /* ADJRequestHandlerTests.m */, 96E5E3A818BBB49E008E7B30 /* ADJTestsUtil.h */, 96E5E3A918BBB49E008E7B30 /* ADJTestsUtil.m */, 96E5E3AA18BBB49E008E7B30 /* InfoPlist.strings */, @@ -571,8 +605,8 @@ 96E5E3AD18BBB49E008E7B30 /* NSURLConnection+NSURLConnectionSynchronousLoadingMocking.m */, 96ED00371A38A4CD00209110 /* ADJAttributionHandlerMock.h */, 96ED00381A38A4CD00209110 /* ADJAttributionHandlerMock.m */, - 96ED003C1A38A98C00209110 /* ADJDelegateTest.h */, - 96ED003D1A38A98C00209110 /* ADJDelegateTest.m */, + 96ED003C1A38A98C00209110 /* ADJAttributionChangedDelegate.h */, + 96ED003D1A38A98C00209110 /* ADJAttributionChangedDelegate.m */, 9657BF6E1A3B029000CD6853 /* ADJAttributionHandlerTests.m */, 9620EA431B062D6F009673F1 /* ADJTestActivityPackage.h */, 9620EA441B062D6F009673F1 /* ADJTestActivityPackage.m */, @@ -580,12 +614,17 @@ 9620EA4D1B06401B009673F1 /* ADJTest.m */, 9620EA4F1B065177009673F1 /* ADJPackageFields.h */, 9620EA501B065177009673F1 /* ADJPackageFields.m */, - 96DD62731C52810500DDF56A /* ADJTrackingSucceededDelegate.h */, - 96DD62741C52810500DDF56A /* ADJTrackingSucceededDelegate.m */, - 96DD62781C52818000DDF56A /* ADJTrackingFailedDelegate.h */, - 96DD62791C52818000DDF56A /* ADJTrackingFailedDelegate.m */, 96FDD75C1C566C38009C402E /* NSURLSession+NSURLDataWithRequestMocking.h */, 96FDD75D1C566C38009C402E /* NSURLSession+NSURLDataWithRequestMocking.m */, + 96ED74FD1CD81B3E00C073B8 /* ADJSdkClickHandlerMock.h */, + 96ED74FE1CD81B3E00C073B8 /* ADJSdkClickHandlerMock.m */, + 967D06981CE2272500ECFCE3 /* ADJSessionState.h */, + 967D06991CE2272500ECFCE3 /* ADJSessionState.m */, + 96E7689F1CE6267D005D811C /* ADJDeeplinkDelegate.h */, + 96E768A01CE6267D005D811C /* ADJDeeplinkDelegate.m */, + 96E768A81CE6281F005D811C /* ADJTrackingDelegate.h */, + 96E768A91CE6281F005D811C /* ADJTrackingDelegate.m */, + 96A2D2251CEE0E6200C39DE0 /* ADJSdkClickHandlerTests.m */, ); path = AdjustTests; sourceTree = ""; @@ -593,6 +632,8 @@ 9DA11A321C96D2A200980777 /* Adjust */ = { isa = PBXGroup; children = ( + 96ED74F91CD8085000C073B8 /* ADJSystemProfile.h */, + 96ED74FA1CD8085000C073B8 /* ADJSystemProfile.m */, 9DA11A3B1C96D2A200980777 /* ADJAdditions */, 9DA11A331C96D2A200980777 /* ADJActivityHandler.h */, 9DA11A341C96D2A200980777 /* ADJActivityHandler.m */, @@ -705,6 +746,7 @@ 968173831C3C2D07002AE1DE /* ADJSessionSuccess.h in Headers */, 96BCFBD91AC9934E005A65C5 /* ADJAdjustFactory.h in Headers */, 96BCFBD81AC9934B005A65C5 /* ADJActivityState.h in Headers */, + 965B7F301CC78F6600098639 /* ADJBackoffStrategy.h in Headers */, 96BCFBD61AC99345005A65C5 /* ADJActivityKind.h in Headers */, 96BCFBD71AC99348005A65C5 /* ADJActivityPackage.h in Headers */, 96BCFBD51AC9933E005A65C5 /* ADJActivityHandler.h in Headers */, @@ -712,10 +754,12 @@ 96854A591B1F2779002B2874 /* ADJTimerOnce.h in Headers */, 96BCFBDF1AC99368005A65C5 /* ADJDeviceInfo.h in Headers */, 96BCFBDB1AC99355005A65C5 /* ADJPackageHandler.h in Headers */, + 96164D8C1CCA4DEA009431AB /* ADJSystemProfile.h in Headers */, 96BCFBDA1AC99353005A65C5 /* ADJPackageBuilder.h in Headers */, 96BCFBE01AC9936C005A65C5 /* ADJAttributionHandler.h in Headers */, 96BCFBD31AC99336005A65C5 /* UIDevice+ADJAdditions.h in Headers */, 96BCFBD41AC99338005A65C5 /* NSData+ADJAdditions.h in Headers */, + 96164D721CC8FA73009431AB /* ADJSdkClickHandler.h in Headers */, 96BCFBDE1AC99365005A65C5 /* ADJUtil.h in Headers */, 96BCFBDC1AC99360005A65C5 /* ADJRequestHandler.h in Headers */, 96FCC53A1C186426007BBFE1 /* ADJResponseData.h in Headers */, @@ -733,9 +777,12 @@ 9DA11A871C96D2A200980777 /* ADJLogger.h in Headers */, 9DA11A791C96D2A200980777 /* ADJAttribution.h in Headers */, 9DA11A831C96D2A200980777 /* ADJEventFailure.h in Headers */, + 96ED74FB1CD8085000C073B8 /* ADJSystemProfile.h in Headers */, 9DA11A851C96D2A200980777 /* ADJEventSuccess.h in Headers */, 9DA11A911C96D2A300980777 /* ADJSessionFailure.h in Headers */, 9DA11A931C96D2A300980777 /* ADJSessionSuccess.h in Headers */, + 96164D861CCA4D27009431AB /* ADJBackoffStrategy.h in Headers */, + 96164D841CCA4D10009431AB /* ADJSdkClickHandler.h in Headers */, 9DA11A7F1C96D2A200980777 /* ADJDeviceInfo.h in Headers */, 9DA11A6F1C96D2A200980777 /* ADJActivityState.h in Headers */, 9DA11A771C96D2A200980777 /* ADJAdjustFactory.h in Headers */, @@ -984,8 +1031,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 96164D731CC8FA73009431AB /* ADJSdkClickHandler.m in Sources */, 96854A601B1F278C002B2874 /* ADJTimerCycle.m in Sources */, 96C0EFE01A3EF47A00B39F31 /* NSString+ADJAdditions.m in Sources */, + 96164D8D1CCA4DEA009431AB /* ADJSystemProfile.m in Sources */, 96C0EFE11A3EF47A00B39F31 /* UIDevice+ADJAdditions.m in Sources */, 96E5E38118BBB48A008E7B30 /* Adjust.m in Sources */, 96E5E38B18BBB48A008E7B30 /* ADJActivityHandler.m in Sources */, @@ -993,6 +1042,7 @@ 96FCC53B1C186426007BBFE1 /* ADJResponseData.m in Sources */, 96E5E39918BBB48A008E7B30 /* ADJUtil.m in Sources */, 96E5E38C18BBB48A008E7B30 /* ADJActivityKind.m in Sources */, + 965B7F311CC78F6600098639 /* ADJBackoffStrategy.m in Sources */, 96C93DF51AC47F2E00B53F56 /* NSData+ADJAdditions.m in Sources */, 96E5E38D18BBB48A008E7B30 /* ADJActivityPackage.m in Sources */, 968173881C3C2D36002AE1DE /* ADJSessionFailure.m in Sources */, @@ -1021,20 +1071,23 @@ 96E5E3B118BBB49E008E7B30 /* ADJLoggerMock.m in Sources */, 96E5E3AF18BBB49E008E7B30 /* ADJActivityHandlerMock.m in Sources */, 96E5E3B318BBB49E008E7B30 /* ADJPackageHandlerTests.m in Sources */, - 96DD627A1C52818000DDF56A /* ADJTrackingFailedDelegate.m in Sources */, 9620EA451B062D6F009673F1 /* ADJTestActivityPackage.m in Sources */, + 96E768A11CE6267D005D811C /* ADJDeeplinkDelegate.m in Sources */, 96E5E3B618BBB49E008E7B30 /* ADJTestsUtil.m in Sources */, - 96DD62751C52810500DDF56A /* ADJTrackingSucceededDelegate.m in Sources */, 96CD2BE11A13BFC600A40AFB /* UIDevice+ADJAdditions.m in Sources */, 96FDD75E1C566C38009C402E /* NSURLSession+NSURLDataWithRequestMocking.m in Sources */, 9620EA511B065177009673F1 /* ADJPackageFields.m in Sources */, + 967D069A1CE2272500ECFCE3 /* ADJSessionState.m in Sources */, 9620EA4E1B06401B009673F1 /* ADJTest.m in Sources */, + 96A2D2381CEF3B2D00C39DE0 /* ADJRequestHandlerTests.m in Sources */, 96E5E3B018BBB49E008E7B30 /* ADJActivityHandlerTests.m in Sources */, - 96ED003E1A38A98C00209110 /* ADJDelegateTest.m in Sources */, + 96E768AA1CE6281F005D811C /* ADJTrackingDelegate.m in Sources */, + 96ED003E1A38A98C00209110 /* ADJAttributionChangedDelegate.m in Sources */, + 96A2D2261CEE0E6200C39DE0 /* ADJSdkClickHandlerTests.m in Sources */, 96CD2BE01A13BFC600A40AFB /* NSString+ADJAdditions.m in Sources */, 96E5E3B818BBB49E008E7B30 /* NSURLConnection+NSURLConnectionSynchronousLoadingMocking.m in Sources */, + 96ED74FF1CD81B3E00C073B8 /* ADJSdkClickHandlerMock.m in Sources */, 96E5E3B418BBB49E008E7B30 /* ADJRequestHandlerMock.m in Sources */, - 96E5E3B518BBB49E008E7B30 /* AIRequestHandlerTests.m in Sources */, 96ED00391A38A4CD00209110 /* ADJAttributionHandlerMock.m in Sources */, 96C93DF61AC47FE000B53F56 /* NSData+ADJAdditions.m in Sources */, 96E5E3B218BBB49E008E7B30 /* ADJPackageHandlerMock.m in Sources */, @@ -1045,7 +1098,10 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 96164D891CCA4D48009431AB /* ADJBackoffStrategy.m in Sources */, + 96164D871CCA4D36009431AB /* ADJSdkClickHandler.m in Sources */, 9DA11A7E1C96D2A200980777 /* ADJConfig.m in Sources */, + 96ED74FC1CD8085000C073B8 /* ADJSystemProfile.m in Sources */, 9DA11A9C1C96D2A300980777 /* ADJUtil.m in Sources */, 9DA11A7C1C96D2A200980777 /* ADJAttributionHandler.m in Sources */, 9DA11A961C96D2A300980777 /* ADJTimerCycle.m in Sources */, diff --git a/Adjust/ADJActivityHandler.h b/Adjust/ADJActivityHandler.h index 469156829..e24895c17 100644 --- a/Adjust/ADJActivityHandler.h +++ b/Adjust/ADJActivityHandler.h @@ -9,12 +9,29 @@ #import "Adjust.h" #import "ADJResponseData.h" +@interface ADJInternalState : NSObject + +@property (nonatomic, assign) BOOL enabled; +@property (nonatomic, assign) BOOL offline; +@property (nonatomic, assign) BOOL background; + +- (id)init; + +- (BOOL)isEnabled; +- (BOOL)isDisabled; +- (BOOL)isOffline; +- (BOOL)isOnline; +- (BOOL)isBackground; +- (BOOL)isForeground; + +@end + @protocol ADJActivityHandler - (id)initWithConfig:(ADJConfig *)adjustConfig; -- (void)trackSubsessionStart; -- (void)trackSubsessionEnd; +- (void)applicationDidBecomeActive; +- (void)applicationWillResignActive; - (void)trackEvent:(ADJEvent *)event; @@ -24,10 +41,10 @@ - (void)launchAttributionResponseTasks:(ADJAttributionResponseData *)attributionResponseData; - (void)setEnabled:(BOOL)enabled; - (BOOL)isEnabled; + - (void)appWillOpenUrl:(NSURL*)url; - (void)setDeviceToken:(NSData *)deviceToken; -- (ADJAttribution*) attribution; - (void)setAttribution:(ADJAttribution*)attribution; - (void)setAskingAttribution:(BOOL)askingAttribution; @@ -38,11 +55,13 @@ retriesLeft:(int)retriesLeft; - (void) setOfflineMode:(BOOL)offline; +- (ADJInternalState*) internalState; @end @interface ADJActivityHandler : NSObject + (id)handlerWithConfig:(ADJConfig *)adjustConfig; +- (ADJAttribution*) attribution; @end diff --git a/Adjust/ADJActivityHandler.m b/Adjust/ADJActivityHandler.m index 4d0f06a83..9ad78b6f4 100644 --- a/Adjust/ADJActivityHandler.m +++ b/Adjust/ADJActivityHandler.m @@ -13,36 +13,64 @@ #import "ADJPackageHandler.h" #import "ADJLogger.h" #import "ADJTimerCycle.h" +#import "ADJTimerOnce.h" #import "ADJUtil.h" #import "UIDevice+ADJAdditions.h" #import "ADJAdjustFactory.h" #import "ADJAttributionHandler.h" #import "NSString+ADJAdditions.h" +#import "ADJSdkClickHandler.h" static NSString * const kActivityStateFilename = @"AdjustIoActivityState"; static NSString * const kAttributionFilename = @"AdjustIoAttribution"; static NSString * const kAdjustPrefix = @"adjust_"; static const char * const kInternalQueueName = "io.adjust.ActivityQueue"; +static NSString * const kForegroundTimerName = @"Foreground timer"; +static NSString * const kBackgroundTimerName = @"Background timer"; + +static NSTimeInterval kForegroundTimerInterval; +static NSTimeInterval kForegroundTimerStart; +static NSTimeInterval kBackgroundTimerInterval; +static double kSessionInterval; +static double kSubSessionInterval; + // number of tries static const int kTryIadV3 = 2; static const uint64_t kDelayRetryIad = 2 * NSEC_PER_SEC; // 1 second +@implementation ADJInternalState + +- (id)init { + self = [super init]; + if (self == nil) return nil; + + return self; +} + +- (BOOL)isEnabled { return self.enabled; } +- (BOOL)isDisabled { return !self.enabled; } +- (BOOL)isOffline { return self.offline; } +- (BOOL)isOnline { return !self.offline; } +- (BOOL)isBackground { return self.background; } +- (BOOL)isForeground { return !self.background; } + +@end + #pragma mark - @interface ADJActivityHandler() @property (nonatomic) dispatch_queue_t internalQueue; @property (nonatomic, retain) id packageHandler; @property (nonatomic, retain) id attributionHandler; +@property (nonatomic, retain) id sdkClickHandler; @property (nonatomic, retain) ADJActivityState *activityState; -@property (nonatomic, retain) ADJTimerCycle *timer; +@property (nonatomic, retain) ADJTimerCycle *foregroundTimer; +@property (nonatomic, retain) ADJTimerOnce *backgroundTimer; @property (nonatomic, retain) id logger; @property (nonatomic, weak) NSObject *adjustDelegate; @property (nonatomic, copy) ADJAttribution *attribution; @property (nonatomic, copy) ADJConfig *adjustConfig; - -@property (nonatomic, assign) BOOL enabled; -@property (nonatomic, assign) BOOL offline; - +@property (nonatomic, retain) ADJInternalState *internalState; @property (nonatomic, copy) ADJDeviceInfo* deviceInfo; @end @@ -60,7 +88,6 @@ @implementation ADJActivityHandler return [[ADJActivityHandler alloc] initWithConfig:adjustConfig]; } - - (id)initWithConfig:(ADJConfig *)adjustConfig { self = [super init]; if (self == nil) return nil; @@ -78,32 +105,88 @@ - (id)initWithConfig:(ADJConfig *)adjustConfig { self.adjustConfig = adjustConfig; self.adjustDelegate = adjustConfig.delegate; + // init logger to be available everywhere self.logger = ADJAdjustFactory.logger; - [self addNotificationObserver]; - self.internalQueue = dispatch_queue_create(kInternalQueueName, DISPATCH_QUEUE_SERIAL); - _enabled = YES; + if ([self.adjustConfig.environment isEqualToString:ADJEnvironmentProduction]) { + [self.logger setLogLevel:ADJLogLevelAssert]; + } else { + [self.logger setLogLevel:self.adjustConfig.logLevel]; + } + + // read files to have sync values available + [self readAttribution]; + [self readActivityState]; + + self.internalState = [[ADJInternalState alloc] init]; + // enabled by default + if (self.activityState == nil) { + self.internalState.enabled = YES; + } else { + self.internalState.enabled = self.activityState.enabled; + } + + // online by default + self.internalState.offline = NO; + // in the background by default + self.internalState.background = YES; + + self.internalQueue = dispatch_queue_create(kInternalQueueName, DISPATCH_QUEUE_SERIAL); dispatch_async(self.internalQueue, ^{ [self initInternal]; }); + // get timer values + kForegroundTimerStart = ADJAdjustFactory.timerStart; + kForegroundTimerInterval = ADJAdjustFactory.timerInterval; + kBackgroundTimerInterval = ADJAdjustFactory.timerInterval; + + // initialize timers to be available in applicationDidBecomeActive/WillResignActive + // after initInternal so that the handlers are initialized + self.foregroundTimer = [ADJTimerCycle timerWithBlock:^{ [self foregroundTimerFired]; } + queue:self.internalQueue + startTime:kForegroundTimerStart + intervalTime:kForegroundTimerInterval + name:kForegroundTimerName]; + + self.backgroundTimer = [ADJTimerOnce timerWithBlock:^{ [self backgroundTimerFired]; } + queue:self.internalQueue + name:kBackgroundTimerName]; + + [self addNotificationObserver]; + return self; } -- (void)trackSubsessionStart { +- (void)applicationDidBecomeActive { + self.internalState.background = NO; + dispatch_async(self.internalQueue, ^{ + [self stopBackgroundTimer]; + + [self startForegroundTimer]; + + [self.logger verbose:@"Subsession start"]; + [self startInternal]; }); } -- (void)trackSubsessionEnd { +- (void)applicationWillResignActive { + self.internalState.background = YES; + dispatch_async(self.internalQueue, ^{ + [self stopForegroundTimer]; + + [self startBackgroundTimer]; + + [self.logger verbose:@"Subsession end"]; + [self endInternal]; }); } -- (void)trackEvent:(ADJEvent *)event -{ +- (void)trackEvent:(ADJEvent *)event { dispatch_async(self.internalQueue, ^{ [self eventInternal:event]; }); @@ -141,19 +224,8 @@ - (void)launchAttributionResponseTasks:(ADJAttributionResponseData *)attribution }); } -- (void)launchDeepLink:(NSString *)deepLink{ - if (deepLink == nil) return; - - NSURL* deepLinkUrl = [NSURL URLWithString:deepLink]; - - BOOL success = [[UIApplication sharedApplication] openURL:deepLinkUrl]; - - if (!success) { - [self.logger error:@"Unable to open deep link (%@)", deepLink]; - } -} - - (void)setEnabled:(BOOL)enabled { + // compare with the saved or internal state if (![self hasChangedState:[self isEnabled] nextState:enabled trueMessage:@"Adjust already enabled" @@ -162,20 +234,30 @@ - (void)setEnabled:(BOOL)enabled { return; } - _enabled = enabled; - if (self.activityState != nil) { - self.activityState.enabled = enabled; - [self writeActivityState]; + // save new enabled state in internal state + self.internalState.enabled = enabled; + + if (self.activityState == nil) { + [self updateState:!enabled + pausingMessage:@"Package handler and attribution handler will start as paused due to the SDK being disabled" + remainsPausedMessage:@"Package and attribution handler will still start as paused due to the SDK being offline" + unPausingMessage:@"Package handler and attribution handler will start as active due to the SDK being enabled"]; + return; } + // save new enabled state in activity state + self.activityState.enabled = enabled; + [self writeActivityState]; + [self updateState:!enabled - pausingMessage:@"Pausing package handler and attribution handler to disable the SDK" - remainsPausedMessage:@"Package and attribution handler remain paused due to the SDK is offline" - unPausingMessage:@"Resuming package handler and attribution handler to enabled the SDK"]; + pausingMessage:@"Pausing package handler and attribution handler due to SDK being disabled" + remainsPausedMessage:@"Package and attribution handler remain paused due to SDK being offline" + unPausingMessage:@"Resuming package handler and attribution handler due to SDK being enabled"]; } - (void)setOfflineMode:(BOOL)offline { - if (![self hasChangedState:self.offline + // compare with the internal state + if (![self hasChangedState:[self.internalState isOffline] nextState:offline trueMessage:@"Adjust already in offline mode" falseMessage:@"Adjust already in online mode"]) @@ -183,19 +265,28 @@ - (void)setOfflineMode:(BOOL)offline { return; } - self.offline = offline; + // save new offline state in internal state + self.internalState.offline = offline; + + if (self.activityState == nil) { + [self updateState:offline + pausingMessage:@"Package handler and attribution handler will start paused due to SDK being offline" + remainsPausedMessage:@"Package and attribution handler will still start as paused due to SDK being disabled" + unPausingMessage:@"Package handler and attribution handler will start as active due to SDK being online"]; + return; + } [self updateState:offline - pausingMessage:@"Pausing package and attribution handler to put in offline mode" - remainsPausedMessage:@"Package and attribution handler remain paused because the SDK is disabled" - unPausingMessage:@"Resuming package handler and attribution handler to put in online mode"]; + pausingMessage:@"Pausing package and attribution handler to put SDK offline mode" + remainsPausedMessage:@"Package and attribution handler remain paused due to SDK being disabled" + unPausingMessage:@"Resuming package handler and attribution handler to put SDK in online mode"]; } - (BOOL)isEnabled { if (self.activityState != nil) { return self.activityState.enabled; } else { - return _enabled; + return [self.internalState isEnabled]; } } @@ -222,18 +313,22 @@ - (void)updateState:(BOOL)pausingState remainsPausedMessage:(NSString *)remainsPausedMessage unPausingMessage:(NSString *)unPausingMessage { + // it is changing from an active state to a pause state if (pausingState) { [self.logger info:pausingMessage]; - [self trackSubsessionEnd]; + [self updateHandlersStatusAndSend]; return; } + // it is remaining in a pause state if ([self paused]) { [self.logger info:remainsPausedMessage]; - } else { - [self.logger info:unPausingMessage]; - [self trackSubsessionStart]; + return; } + + // it is changing from a pause state to an active state + [self.logger info:unPausingMessage]; + [self updateHandlersStatusAndSend]; } - (void)appWillOpenUrl:(NSURL*)url { @@ -268,8 +363,7 @@ - (void)setIadDate:(NSDate *)iAdImpressionDate withPurchaseDate:(NSDate *)appPur clickBuilder.clickTime = iAdImpressionDate; ADJActivityPackage *clickPackage = [clickBuilder buildClickPackage:@"iad"]; - [self.packageHandler addPackage:clickPackage]; - [self.packageHandler sendFirstPackage]; + [self.sdkClickHandler sendSdkClick:clickPackage]; } - (void)setIadDetails:(NSDictionary *)attributionDetails @@ -307,8 +401,7 @@ - (void)setIadDetails:(NSDictionary *)attributionDetails clickBuilder.iadDetails = attributionDetails; ADJActivityPackage *clickPackage = [clickBuilder buildClickPackage:@"iad3"]; - [self.packageHandler addPackage:clickPackage]; - [self.packageHandler sendFirstPackage]; + [self.sdkClickHandler sendSdkClick:clickPackage]; } - (void)setAskingAttribution:(BOOL)askingAttribution { @@ -316,35 +409,42 @@ - (void)setAskingAttribution:(BOOL)askingAttribution { [self writeActivityState]; } -- (void)updateStatus { +- (void)updateHandlersStatusAndSend { + dispatch_async(self.internalQueue, ^{ + [self updateHandlersStatusAndSendInternal]; + }); +} + +- (void)foregroundTimerFired { + dispatch_async(self.internalQueue, ^{ + [self foregroundTimerFiredInternal]; + }); +} + +- (void)backgroundTimerFired { dispatch_async(self.internalQueue, ^{ - [self updateStatusInternal]; + [self backgroundTimerFiredInternal]; }); } #pragma mark - internal - (void)initInternal { - self.deviceInfo = [ADJDeviceInfo deviceInfoWithSdkPrefix:self.adjustConfig.sdkPrefix]; + kSessionInterval = ADJAdjustFactory.sessionInterval; + kSubSessionInterval = ADJAdjustFactory.subsessionInterval; - if ([self.adjustConfig.environment isEqualToString:ADJEnvironmentProduction]) { - [self.logger setLogLevel:ADJLogLevelAssert]; - } else { - [self.logger setLogLevel:self.adjustConfig.logLevel]; - } + self.deviceInfo = [ADJDeviceInfo deviceInfoWithSdkPrefix:self.adjustConfig.sdkPrefix]; if (self.adjustConfig.eventBufferingEnabled) { [self.logger info:@"Event buffering is enabled"]; } if (self.adjustConfig.defaultTracker != nil) { - [self.logger info:@"Default tracker: %@", self.adjustConfig.defaultTracker]; + [self.logger info:@"Default tracker: '%@'", self.adjustConfig.defaultTracker]; } - [self readAttribution]; - [self readActivityState]; - + BOOL toSend = [self toSend]; self.packageHandler = [ADJAdjustFactory packageHandlerForActivityHandler:self - startPaused:[self paused]]; + startsSending:toSend]; double now = [NSDate.date timeIntervalSince1970]; ADJPackageBuilder *attributionBuilder = [[ADJPackageBuilder alloc] @@ -355,16 +455,12 @@ - (void)initInternal { ADJActivityPackage *attributionPackage = [attributionBuilder buildAttributionPackage]; self.attributionHandler = [ADJAdjustFactory attributionHandlerForActivityHandler:self withAttributionPackage:attributionPackage - startPaused:[self paused] - hasAttributionChangedDelegate:self.adjustConfig.hasAttributionChangedDelegate]; + startsSending:toSend + hasAttributionChangedDelegate:self.adjustConfig.hasAttributionChangedDelegate]; - self.timer = [ADJTimerCycle timerWithBlock:^{ [self timerFiredInternal]; } - queue:self.internalQueue - startTime:ADJAdjustFactory.timerStart - intervalTime:ADJAdjustFactory.timerInterval]; - [[UIDevice currentDevice] adjSetIad:self triesV3Left:kTryIadV3]; + self.sdkClickHandler = [ADJAdjustFactory sdkClickHandlerWithStartsPaused:toSend]; - [self startInternal]; + [[UIDevice currentDevice] adjSetIad:self triesV3Left:kTryIadV3]; } - (void)startInternal { @@ -374,13 +470,11 @@ - (void)startInternal { return; } - [self updateStatusInternal]; + [self updateHandlersStatusAndSendInternal]; [self processSession]; [self checkAttributionState]; - - [self startTimer]; } - (void)processSession { @@ -393,7 +487,7 @@ - (void)processSession { [self transferSessionPackage:now]; [self.activityState resetSessionAttributes:now]; - self.activityState.enabled = _enabled; + self.activityState.enabled = [self.internalState isEnabled]; [self writeActivityState]; return; } @@ -407,7 +501,7 @@ - (void)processSession { } // new session - if (lastInterval > ADJAdjustFactory.sessionInterval) { + if (lastInterval > kSessionInterval) { self.activityState.sessionCount++; self.activityState.lastInterval = lastInterval; @@ -418,18 +512,23 @@ - (void)processSession { } // new subsession - if (lastInterval > ADJAdjustFactory.subsessionInterval) { + if (lastInterval > kSubSessionInterval) { self.activityState.subsessionCount++; self.activityState.sessionLength += lastInterval; self.activityState.lastActivity = now; - [self writeActivityState]; - [self.logger info:@"Started subsession %d of session %d", + [self.logger verbose:@"Started subsession %d of session %d", self.activityState.subsessionCount, self.activityState.sessionCount]; + [self writeActivityState]; + return; } + + [self.logger verbose:@"Time span since last activity too short for a new subsession"]; } - (void)checkAttributionState { + if (![self checkActivityState]) return; + // if it' a new session if (self.activityState.subsessionCount <= 1) { return; @@ -444,16 +543,19 @@ - (void)checkAttributionState { } - (void)endInternal { - [self.packageHandler pauseSending]; - [self.attributionHandler pauseSending]; - [self stopTimer]; + // pause sending if it's not allowed to send + if (![self toSend]) { + [self pauseSending]; + } + double now = [NSDate.date timeIntervalSince1970]; - [self updateActivityState:now]; - [self writeActivityState]; + if ([self updateActivityState:now]) { + [self writeActivityState]; + } } -- (void)eventInternal:(ADJEvent *)event -{ +- (void)eventInternal:(ADJEvent *)event { + if (![self checkActivityState]) return; if (![self isEnabled]) return; if (![self checkEvent:event]) return; if (![self checkTransactionId:event.transactionId]) return; @@ -478,6 +580,11 @@ - (void)eventInternal:(ADJEvent *)event [self.packageHandler sendFirstPackage]; } + // if it is in the background and it can send, start the background timer + if (self.adjustConfig.sendInBackground && [self.internalState isBackground]) { + [self startBackgroundTimer]; + } + [self writeActivityState]; } @@ -487,9 +594,9 @@ - (void) launchEventResponseTasksInternal:(ADJEventResponseData *)eventResponseD && [self.adjustDelegate respondsToSelector:@selector(adjustEventTrackingSucceeded:)]) { [self.logger debug:@"Launching success event tracking delegate"]; - [self.adjustDelegate performSelectorOnMainThread:@selector(adjustEventTrackingSucceeded:) - withObject:[eventResponseData successResponseData] - waitUntilDone:NO]; // non-blocking + [ADJUtil launchInMainThread:self.adjustDelegate + selector:@selector(adjustEventTrackingSucceeded:) + withObject:[eventResponseData successResponseData]]; return; } // event failure callback @@ -497,9 +604,9 @@ - (void) launchEventResponseTasksInternal:(ADJEventResponseData *)eventResponseD && [self.adjustDelegate respondsToSelector:@selector(adjustEventTrackingFailed:)]) { [self.logger debug:@"Launching failed event tracking delegate"]; - [self.adjustDelegate performSelectorOnMainThread:@selector(adjustEventTrackingFailed:) - withObject:[eventResponseData failureResponseData] - waitUntilDone:NO]; // non-blocking + [ADJUtil launchInMainThread:self.adjustDelegate + selector:@selector(adjustEventTrackingFailed:) + withObject:[eventResponseData failureResponseData]]; return; } } @@ -512,42 +619,64 @@ - (void) launchSessionResponseTasksInternal:(ADJSessionResponseData *)sessionRes && [self.adjustDelegate respondsToSelector:@selector(adjustSessionTrackingSucceeded:)]) { [self.logger debug:@"Launching success session tracking delegate"]; - [self.adjustDelegate performSelectorOnMainThread:@selector(adjustSessionTrackingSucceeded:) - withObject:[sessionResponseData successResponseData] - waitUntilDone:NO]; // non-blocking + [ADJUtil launchInMainThread:self.adjustDelegate + selector:@selector(adjustSessionTrackingSucceeded:) + withObject:[sessionResponseData successResponseData]]; } // session failure callback if (!sessionResponseData.success && [self.adjustDelegate respondsToSelector:@selector(adjustSessionTrackingFailed:)]) { [self.logger debug:@"Launching failed session tracking delegate"]; - [self.adjustDelegate performSelectorOnMainThread:@selector(adjustSessionTrackingFailed:) - withObject:[sessionResponseData failureResponseData] - waitUntilDone:NO]; // non-blocking + [ADJUtil launchInMainThread:self.adjustDelegate + selector:@selector(adjustSessionTrackingFailed:) + withObject:[sessionResponseData failureResponseData]]; } - // try to update and launch the attribution changed delegate blocking + // try to update and launch the attribution changed delegate if (toLaunchAttributionDelegate) { [self.logger debug:@"Launching attribution changed delegate"]; - [self.adjustDelegate performSelectorOnMainThread:@selector(adjustAttributionChanged:) - withObject:sessionResponseData.attribution - waitUntilDone:NO]; // non-blocking + [ADJUtil launchInMainThread:self.adjustDelegate + selector:@selector(adjustAttributionChanged:) + withObject:sessionResponseData.attribution]; } - if ([ADJUtil isNull:sessionResponseData.jsonResponse]) { + [self prepareDeeplink:sessionResponseData]; +} + +- (void)prepareDeeplink:(ADJResponseData *)responseData { + if (responseData == nil) { return; } - NSString *deepLink = [sessionResponseData.jsonResponse objectForKey:@"deeplink"]; + NSString *deepLink = [responseData.jsonResponse objectForKey:@"deeplink"]; if (deepLink == nil) { return; } - [self.logger info:@"Trying to open deep link (%@)", deepLink]; + NSURL* deepLinkUrl = [NSURL URLWithString:deepLink]; + + [self.logger info:@"Open deep link (%@)", deepLink]; + + [ADJUtil launchInMainThread:^{ + BOOL toLaunchDeeplink = YES; + + if ([self.adjustDelegate respondsToSelector:@selector(adjustDeeplinkResponse:)]) { + toLaunchDeeplink = [self.adjustDelegate adjustDeeplinkResponse:deepLinkUrl]; + } + + if (toLaunchDeeplink) { + [self launchDeepLinkMain:deepLinkUrl]; + } + }]; +} + +- (void)launchDeepLinkMain:(NSURL *) deepLinkUrl{ + BOOL success = [[UIApplication sharedApplication] openURL:deepLinkUrl]; - [self performSelectorOnMainThread:@selector(launchDeepLink:) - withObject:deepLink - waitUntilDone:NO]; // non-blocking + if (!success) { + [self.logger error:@"Unable to open deep link (%@)", deepLinkUrl]; + } } - (void) launchAttributionResponseTasksInternal:(ADJAttributionResponseData *)attributionResponseData { @@ -556,9 +685,9 @@ - (void) launchAttributionResponseTasksInternal:(ADJAttributionResponseData *)at // try to update and launch the attribution changed delegate non-blocking if (toLaunchAttributionDelegate) { [self.logger debug:@"Launching attribution changed delegate"]; - [self.adjustDelegate performSelectorOnMainThread:@selector(adjustAttributionChanged:) - withObject:attributionResponseData.attribution - waitUntilDone:NO]; // non-blocking + [ADJUtil launchInMainThread:self.adjustDelegate + selector:@selector(adjustAttributionChanged:) + withObject:attributionResponseData.attribution]; } } @@ -576,6 +705,10 @@ - (BOOL)updateAttribution:(ADJAttribution *)attribution { return NO; } + if (![self.adjustConfig hasAttributionChangedDelegate]) { + return NO; + } + if (![self.adjustDelegate respondsToSelector:@selector(adjustAttributionChanged:)]) { return NO; } @@ -588,23 +721,20 @@ - (void) appWillOpenUrlInternal:(NSURL *)url { return; } + if ([[url absoluteString] length] == 0) { + return; + } + NSArray* queryArray = [url.query componentsSeparatedByString:@"&"]; if (queryArray == nil) { - return; + queryArray = @[]; } NSMutableDictionary* adjustDeepLinks = [NSMutableDictionary dictionary]; ADJAttribution *deeplinkAttribution = [[ADJAttribution alloc] init]; - BOOL hasDeepLink = NO; for (NSString* fieldValuePair in queryArray) { - if([self readDeeplinkQueryString:fieldValuePair adjustDeepLinks:adjustDeepLinks attribution:deeplinkAttribution]) { - hasDeepLink = YES; - } - } - - if (!hasDeepLink) { - return; + [self readDeeplinkQueryString:fieldValuePair adjustDeepLinks:adjustDeepLinks attribution:deeplinkAttribution]; } double now = [NSDate.date timeIntervalSince1970]; @@ -616,10 +746,10 @@ - (void) appWillOpenUrlInternal:(NSURL *)url { clickBuilder.deeplinkParameters = adjustDeepLinks; clickBuilder.attribution = deeplinkAttribution; clickBuilder.clickTime = [NSDate date]; + clickBuilder.deeplink = [url absoluteString]; ADJActivityPackage *clickPackage = [clickBuilder buildClickPackage:@"deeplink"]; - [self.packageHandler addPackage:clickPackage]; - [self.packageHandler sendFirstPackage]; + [self.sdkClickHandler sendSdkClick:clickPackage]; } - (BOOL) readDeeplinkQueryString:(NSString *)queryString @@ -676,38 +806,67 @@ - (BOOL) trySetAttributionDeeplink:(ADJAttribution *)deeplinkAttribution return NO; } -- (void) setDeviceTokenInternal:(NSData *)deviceToken { +- (void)setDeviceTokenInternal:(NSData *)deviceToken { if (deviceToken == nil) { return; } - NSString *token = [deviceToken.description stringByTrimmingCharactersInSet: + NSString *deviceTokenString = [deviceToken.description stringByTrimmingCharactersInSet: [NSCharacterSet characterSetWithCharactersInString:@"<>"]]; - token = [token stringByReplacingOccurrencesOfString:@" " withString:@""]; + deviceTokenString = [deviceTokenString stringByReplacingOccurrencesOfString:@" " withString:@""]; + + if (![self updateDeviceToken:deviceTokenString]) { + return; + } + + double now = [NSDate.date timeIntervalSince1970]; + ADJPackageBuilder * clickBuilder = [[ADJPackageBuilder alloc] + initWithDeviceInfo:self.deviceInfo + activityState:self.activityState + config:self.adjustConfig + createdAt:now]; + + clickBuilder.deviceToken = deviceTokenString; + + ADJActivityPackage * clickPackage = [clickBuilder buildClickPackage:@"push"]; + + [self.sdkClickHandler sendSdkClick:clickPackage]; +} - self.deviceInfo.pushToken = token; +- (BOOL)updateDeviceToken:(NSString *)deviceToken { + if (deviceToken == nil) { + return NO; + } + + if ([deviceToken isEqualToString:self.activityState.deviceToken]) { + return NO; + } + + return YES; } #pragma mark - private // returns whether or not the activity state should be written - (BOOL)updateActivityState:(double)now { + if (![self checkActivityState]) return NO; + double lastInterval = now - self.activityState.lastActivity; + // ignore late updates + if (lastInterval > kSessionInterval) return NO; + + self.activityState.lastActivity = now; + if (lastInterval < 0) { [self.logger error:@"Time travel!"]; - self.activityState.lastActivity = now; return YES; + } else { + self.activityState.sessionLength += lastInterval; + self.activityState.timeSpent += lastInterval; } - // ignore late updates - if (lastInterval > ADJAdjustFactory.sessionInterval) return NO; - - self.activityState.sessionLength += lastInterval; - self.activityState.timeSpent += lastInterval; - self.activityState.lastActivity = now; - - return (lastInterval > ADJAdjustFactory.subsessionInterval); + return YES; } - (void)writeActivityState { @@ -743,52 +902,74 @@ - (void)transferSessionPackage:(double)now { } # pragma mark - handlers status -- (void)updateStatusInternal { - [self updateAttributionHandlerStatus]; - [self updatePackageHandlerStatus]; -} +- (void)updateHandlersStatusAndSendInternal { + // check if it should stop sending -- (void)updateAttributionHandlerStatus { - if ([self paused]) { - [self.attributionHandler pauseSending]; - } else { - [self.attributionHandler resumeSending]; + if (![self toSend]) { + [self pauseSending]; + return; } -} -- (void)updatePackageHandlerStatus { - if ([self paused]) { - [self.packageHandler pauseSending]; - } else { - [self.packageHandler resumeSending]; + [self resumeSending]; + + // try to send + if (!self.adjustConfig.eventBufferingEnabled) { + [self.packageHandler sendFirstPackage]; } } +- (void)pauseSending { + [self.attributionHandler pauseSending]; + [self.packageHandler pauseSending]; + [self.sdkClickHandler pauseSending]; +} + +- (void)resumeSending { + [self.attributionHandler resumeSending]; + [self.packageHandler resumeSending]; + [self.sdkClickHandler resumeSending]; +} + +// offline or disabled pauses the sdk - (BOOL)paused { - return self.offline || !self.isEnabled; + return [self.internalState isOffline] || ![self isEnabled]; +} + +- (BOOL)toSend { + // if it's offline, disabled -> don't send + if ([self paused]) { + return NO; + } + + // has the option to send in the background -> is to send + if (self.adjustConfig.sendInBackground) { + return YES; + } + + // doesn't have the option -> depends on being on the background/foreground + return [self.internalState isForeground]; } # pragma mark - timer -- (void)startTimer { +- (void)startForegroundTimer { // don't start the timer if it's disabled/offline if ([self paused]) { return; } - [self.timer resume]; + [self.foregroundTimer resume]; } -- (void)stopTimer { - [self.timer suspend]; +- (void)stopForegroundTimer { + [self.foregroundTimer suspend]; } -- (void)timerFiredInternal { +- (void)foregroundTimerFiredInternal { if ([self paused]) { // stop the timer cycle if it's disabled/offline - [self stopTimer]; + [self stopForegroundTimer]; return; } - [self.logger debug:@"Session timer fired"]; [self.packageHandler sendFirstPackage]; double now = [NSDate.date timeIntervalSince1970]; if ([self updateActivityState:now]) { @@ -796,18 +977,40 @@ - (void)timerFiredInternal { } } +- (void)startBackgroundTimer { + // check if it can send in the background + if (![self toSend]) { + return; + } + + // background timer already started + if ([self.backgroundTimer fireIn] > 0) { + return; + } + + [self.backgroundTimer startIn:kBackgroundTimerInterval]; +} + +- (void)stopBackgroundTimer { + [self.backgroundTimer cancel]; +} + +-(void)backgroundTimerFiredInternal { + [self.packageHandler sendFirstPackage]; +} + #pragma mark - notifications - (void)addNotificationObserver { NSNotificationCenter *center = NSNotificationCenter.defaultCenter; [center removeObserver:self]; [center addObserver:self - selector:@selector(trackSubsessionStart) + selector:@selector(applicationDidBecomeActive) name:UIApplicationDidBecomeActiveNotification object:nil]; [center addObserver:self - selector:@selector(trackSubsessionEnd) + selector:@selector(applicationWillResignActive) name:UIApplicationWillResignActiveNotification object:nil]; @@ -854,4 +1057,11 @@ - (BOOL)checkEvent:(ADJEvent *)event { return YES; } +- (BOOL)checkActivityState { + if (self.activityState == nil) { + [self.logger error:@"Missing activity state"]; + return NO; + } + return YES; +} @end diff --git a/Adjust/ADJActivityPackage.h b/Adjust/ADJActivityPackage.h index a6b84a84e..e740fbb35 100644 --- a/Adjust/ADJActivityPackage.h +++ b/Adjust/ADJActivityPackage.h @@ -15,6 +15,8 @@ @property (nonatomic, copy) NSString *clientSdk; @property (nonatomic, retain) NSDictionary *parameters; +@property (nonatomic, assign) NSInteger retries; + // logs @property (nonatomic, assign) ADJActivityKind activityKind; @property (nonatomic, copy) NSString *suffix; @@ -23,4 +25,7 @@ - (NSString *)successMessage; - (NSString *)failureMessage; +- (NSInteger)getRetries; +- (NSInteger)increaseRetries; + @end diff --git a/Adjust/ADJActivityPackage.m b/Adjust/ADJActivityPackage.m index dff805622..78bbf4b51 100644 --- a/Adjust/ADJActivityPackage.m +++ b/Adjust/ADJActivityPackage.m @@ -49,6 +49,15 @@ - (NSString *)failureMessage { self.suffix]; } +- (NSInteger)getRetries { + return self.retries; +} + +- (NSInteger)increaseRetries { + self.retries = self.retries + 1; + return self.retries; +} + #pragma mark NSCoding - (id)initWithCoder:(NSCoder *)decoder { self = [super init]; diff --git a/Adjust/ADJActivityState.h b/Adjust/ADJActivityState.h index fcd27bbe0..39f82005c 100644 --- a/Adjust/ADJActivityState.h +++ b/Adjust/ADJActivityState.h @@ -13,6 +13,7 @@ @property (nonatomic, copy) NSString *uuid; @property (nonatomic, assign) BOOL enabled; @property (nonatomic, assign) BOOL askingAttribution; +@property (nonatomic, copy) NSString *deviceToken; // global counters @property (nonatomic, assign) int eventCount; diff --git a/Adjust/ADJActivityState.m b/Adjust/ADJActivityState.m index a93914910..080f4d213 100644 --- a/Adjust/ADJActivityState.m +++ b/Adjust/ADJActivityState.m @@ -31,6 +31,7 @@ - (id)init { self.transactionIds = [NSMutableArray arrayWithCapacity:kTransactionIdCount]; self.enabled = YES; self.askingAttribution = NO; + self.deviceToken = nil; return self; } @@ -60,9 +61,9 @@ - (BOOL)findTransactionId:(NSString *)transactionId { } - (NSString *)description { - return [NSString stringWithFormat:@"ec:%d sc:%d ssc:%d ask:%d sl:%.1f ts:%.1f la:%.1f", + return [NSString stringWithFormat:@"ec:%d sc:%d ssc:%d ask:%d sl:%.1f ts:%.1f la:%.1f dt:%@", self.eventCount, self.sessionCount, self.subsessionCount, self.askingAttribution, self.sessionLength, - self.timeSpent, self.lastActivity]; + self.timeSpent, self.lastActivity, self.deviceToken]; } #pragma mark NSCoding @@ -107,6 +108,10 @@ - (id)initWithCoder:(NSCoder *)decoder { self.askingAttribution = NO; } + if ([decoder containsValueForKey:@"deviceToken"]) { + self.deviceToken = [decoder decodeObjectForKey:@"deviceToken"]; + } + self.lastInterval = -1; return self; @@ -123,6 +128,7 @@ - (void)encodeWithCoder:(NSCoder *)encoder { [encoder encodeObject:self.transactionIds forKey:@"transactionIds"]; [encoder encodeBool:self.enabled forKey:@"enabled"]; [encoder encodeBool:self.askingAttribution forKey:@"askingAttribution"]; + [encoder encodeObject:self.deviceToken forKey:@"deviceToken"]; } -(id)copyWithZone:(NSZone *)zone @@ -139,6 +145,7 @@ -(id)copyWithZone:(NSZone *)zone copy.enabled = self.enabled; copy.lastActivity = self.lastActivity; copy.askingAttribution = self.askingAttribution; + copy.deviceToken = self.deviceToken; // transactionIds not copied } diff --git a/Adjust/ADJAdjustFactory.h b/Adjust/ADJAdjustFactory.h index b5bd6d8b0..4c94acea0 100644 --- a/Adjust/ADJAdjustFactory.h +++ b/Adjust/ADJAdjustFactory.h @@ -13,31 +13,43 @@ #import "ADJLogger.h" #import "ADJAttributionHandler.h" #import "ADJActivityPackage.h" +#import "ADJBackoffStrategy.h" +#import "ADJSdkClickHandler.h" @interface ADJAdjustFactory : NSObject + (id)packageHandlerForActivityHandler:(id)activityHandler - startPaused:(BOOL)startPaused; + startsSending:(BOOL)startsSending; + (id)requestHandlerForPackageHandler:(id)packageHandler; + (id)activityHandlerWithConfig:(ADJConfig *)adjustConfig; ++ (id)sdkClickHandlerWithStartsPaused:(BOOL)startsSending; + + (id)logger; + (double)sessionInterval; + (double)subsessionInterval; + (NSTimeInterval)timerInterval; + (NSTimeInterval)timerStart; ++ (ADJBackoffStrategy *)packageHandlerBackoffStrategy; ++ (ADJBackoffStrategy *)sdkClickHandlerBackoffStrategy; + + (id)attributionHandlerForActivityHandler:(id)activityHandler withAttributionPackage:(ADJActivityPackage *) attributionPackage - startPaused:(BOOL)startPaused + startsSending:(BOOL)startsSending hasAttributionChangedDelegate:(BOOL)hasAttributionChangedDelegate; ++ (BOOL)testing; + (void)setPackageHandler:(id)packageHandler; + (void)setRequestHandler:(id)requestHandler; + (void)setActivityHandler:(id)activityHandler; ++ (void)setSdkClickHandler:(id)sdkClickHandler; + (void)setLogger:(id)logger; + (void)setSessionInterval:(double)sessionInterval; + (void)setSubsessionInterval:(double)subsessionInterval; + (void)setTimerInterval:(NSTimeInterval)timerInterval; + (void)setTimerStart:(NSTimeInterval)timerStart; + (void)setAttributionHandler:(id)attributionHandler; ++ (void)setPackageHandlerBackoffStrategy:(ADJBackoffStrategy *)backoffStrategy; ++ (void)setSdkClickHandlerBackoffStrategy:(ADJBackoffStrategy *)backoffStrategy; ++ (void)setTesting:(BOOL)testing; @end diff --git a/Adjust/ADJAdjustFactory.m b/Adjust/ADJAdjustFactory.m index cc6d76aab..7d9f1cbbc 100644 --- a/Adjust/ADJAdjustFactory.m +++ b/Adjust/ADJAdjustFactory.m @@ -13,22 +13,25 @@ static id internalActivityHandler = nil; static id internalLogger = nil; static id internalAttributionHandler = nil; +static id internalSdkClickHandler = nil; static double internalSessionInterval = -1; static double intervalSubsessionInterval = -1; static NSTimeInterval internalTimerInterval = -1; static NSTimeInterval intervalTimerStart = -1; - +static ADJBackoffStrategy * packageHandlerBackoffStrategy = nil; +static ADJBackoffStrategy * sdkClickHandlerBackoffStrategy = nil; +static BOOL internalTesting = NO; @implementation ADJAdjustFactory + (id)packageHandlerForActivityHandler:(id)activityHandler - startPaused:(BOOL)startPaused { + startsSending:(BOOL)startsSending { if (internalPackageHandler == nil) { - return [ADJPackageHandler handlerWithActivityHandler:activityHandler startPaused:startPaused]; + return [ADJPackageHandler handlerWithActivityHandler:activityHandler startsSending:startsSending]; } - return [internalPackageHandler initWithActivityHandler:activityHandler startPaused:startPaused]; + return [internalPackageHandler initWithActivityHandler:activityHandler startsSending:startsSending]; } + (id)requestHandlerForPackageHandler:(id)packageHandler { @@ -76,29 +79,56 @@ + (NSTimeInterval)timerInterval { + (NSTimeInterval)timerStart { if (intervalTimerStart == -1) { - return 0; // 0 seconds + return 60; // 1 minute } return intervalTimerStart; } ++ (ADJBackoffStrategy *)packageHandlerBackoffStrategy { + if (packageHandlerBackoffStrategy == nil) { + return [ADJBackoffStrategy backoffStrategyWithType:ADJLongWait]; + } + return packageHandlerBackoffStrategy; +} + ++ (ADJBackoffStrategy *)sdkClickHandlerBackoffStrategy { + if (sdkClickHandlerBackoffStrategy == nil) { + return [ADJBackoffStrategy backoffStrategyWithType:ADJShortWait]; + } + return sdkClickHandlerBackoffStrategy; +} + + (id)attributionHandlerForActivityHandler:(id)activityHandler withAttributionPackage:(ADJActivityPackage *) attributionPackage - startPaused:(BOOL)startPaused + startsSending:(BOOL)startsSending hasAttributionChangedDelegate:(BOOL)hasAttributionChangedDelegate { if (internalAttributionHandler == nil) { return [ADJAttributionHandler handlerWithActivityHandler:activityHandler withAttributionPackage:attributionPackage - startPaused:startPaused + startsSending:startsSending hasAttributionChangedDelegate:hasAttributionChangedDelegate]; } return [internalAttributionHandler initWithActivityHandler:activityHandler withAttributionPackage:attributionPackage - startPaused:startPaused + startsSending:startsSending hasAttributionChangedDelegate:hasAttributionChangedDelegate]; } ++ (id)sdkClickHandlerWithStartsPaused:(BOOL)startsSending +{ + if (internalSdkClickHandler == nil) { + return [ADJSdkClickHandler handlerWithStartsSending:startsSending]; + } + + return [internalSdkClickHandler initWithStartsSending:startsSending]; +} + ++ (BOOL)testing { + return internalTesting; +} + + (void)setPackageHandler:(id)packageHandler { internalPackageHandler = packageHandler; } @@ -134,4 +164,20 @@ + (void)setTimerStart:(NSTimeInterval)timerStart { + (void)setAttributionHandler:(id)attributionHandler { internalAttributionHandler = attributionHandler; } + ++ (void)setSdkClickHandler:(id)sdkClickHandler { + internalSdkClickHandler = sdkClickHandler; +} + ++ (void)setPackageHandlerBackoffStrategy:(ADJBackoffStrategy *)backoffStrategy { + packageHandlerBackoffStrategy = backoffStrategy; +} + ++ (void)setSdkClickHandlerBackoffStrategy:(ADJBackoffStrategy *)backoffStrategy { + sdkClickHandlerBackoffStrategy = backoffStrategy; +} + ++ (void)setTesting:(BOOL)testing { + internalTesting = testing; +} @end diff --git a/Adjust/ADJAttributionHandler.h b/Adjust/ADJAttributionHandler.h index 594a80d5c..af61954cb 100644 --- a/Adjust/ADJAttributionHandler.h +++ b/Adjust/ADJAttributionHandler.h @@ -14,7 +14,7 @@ - (id)initWithActivityHandler:(id) activityHandler withAttributionPackage:(ADJActivityPackage *) attributionPackage - startPaused:(BOOL)startPaused + startsSending:(BOOL)startsSending hasAttributionChangedDelegate:(BOOL)hasAttributionChangedDelegate; - (void)checkSessionResponse:(ADJSessionResponseData *)sessionResponseData; @@ -33,7 +33,7 @@ hasAttributionChangedDelegate:(BOOL)hasAttributionChangedDelegate; + (id)handlerWithActivityHandler:(id)activityHandler withAttributionPackage:(ADJActivityPackage *) attributionPackage - startPaused:(BOOL)startPaused + startsSending:(BOOL)startsSending hasAttributionChangedDelegate:(BOOL)hasAttributionChangedDelegate; @end diff --git a/Adjust/ADJAttributionHandler.m b/Adjust/ADJAttributionHandler.m index 696d2e534..b8a43d0c6 100644 --- a/Adjust/ADJAttributionHandler.m +++ b/Adjust/ADJAttributionHandler.m @@ -14,16 +14,17 @@ #import "ADJTimerOnce.h" static const char * const kInternalQueueName = "com.adjust.AttributionQueue"; +static NSString * const kAttributionTimerName = @"Attribution timer"; @interface ADJAttributionHandler() @property (nonatomic) dispatch_queue_t internalQueue; @property (nonatomic, assign) id activityHandler; @property (nonatomic, assign) id logger; -@property (nonatomic, retain) ADJTimerOnce *timer; +@property (nonatomic, retain) ADJTimerOnce *attributionTimer; @property (nonatomic, retain) ADJActivityPackage * attributionPackage; @property (nonatomic, assign) BOOL paused; -@property (nonatomic, assign) BOOL hasDelegate; +@property (nonatomic, assign) BOOL hasNeedsResponseDelegate; @end @@ -33,18 +34,18 @@ @implementation ADJAttributionHandler + (id)handlerWithActivityHandler:(id)activityHandler withAttributionPackage:(ADJActivityPackage *) attributionPackage - startPaused:(BOOL)startPaused + startsSending:(BOOL)startsSending hasAttributionChangedDelegate:(BOOL)hasAttributionChangedDelegate; { return [[ADJAttributionHandler alloc] initWithActivityHandler:activityHandler withAttributionPackage:attributionPackage - startPaused:startPaused + startsSending:startsSending hasAttributionChangedDelegate:hasAttributionChangedDelegate]; } - (id)initWithActivityHandler:(id) activityHandler withAttributionPackage:(ADJActivityPackage *) attributionPackage - startPaused:(BOOL)startPaused + startsSending:(BOOL)startsSending hasAttributionChangedDelegate:(BOOL)hasAttributionChangedDelegate; { self = [super init]; @@ -54,10 +55,11 @@ - (id)initWithActivityHandler:(id) activityHandler self.activityHandler = activityHandler; self.logger = ADJAdjustFactory.logger; self.attributionPackage = attributionPackage; - self.paused = startPaused; - self.hasDelegate = hasAttributionChangedDelegate; - self.timer = [ADJTimerOnce timerWithBlock:^{ [self getAttributionInternal]; } - queue:self.internalQueue]; + self.paused = !startsSending; + self.hasNeedsResponseDelegate = hasAttributionChangedDelegate; + self.attributionTimer = [ADJTimerOnce timerWithBlock:^{ [self getAttributionInternal]; } + queue:self.internalQueue + name:kAttributionTimerName]; return self; } @@ -76,7 +78,7 @@ - (void) checkAttributionResponse:(ADJAttributionResponseData *)attributionRespo - (void) getAttributionWithDelay:(int)milliSecondsDelay { NSTimeInterval secondsDelay = milliSecondsDelay / 1000; - NSTimeInterval nextAskIn = [self.timer fireIn]; + NSTimeInterval nextAskIn = [self.attributionTimer fireIn]; if (nextAskIn > secondsDelay) { return; } @@ -86,7 +88,7 @@ - (void) getAttributionWithDelay:(int)milliSecondsDelay { } // set the new time the timer will fire in - [self.timer startIn:secondsDelay]; + [self.attributionTimer startIn:secondsDelay]; } - (void) getAttribution { @@ -136,7 +138,7 @@ - (void) checkAttributionInternal:(ADJResponseData *)responseData { } - (void) getAttributionInternal { - if (!self.hasDelegate) { + if (!self.hasNeedsResponseDelegate) { return; } if (self.paused) { diff --git a/Adjust/ADJBackoffStrategy.h b/Adjust/ADJBackoffStrategy.h new file mode 100644 index 000000000..5c6e7bec6 --- /dev/null +++ b/Adjust/ADJBackoffStrategy.h @@ -0,0 +1,30 @@ +// +// ADJBackoffStrategy.h +// Adjust +// +// Created by Pedro Filipe on 20/04/16. +// Copyright © 2016 adjust GmbH. All rights reserved. +// + +#import + +typedef NS_ENUM(NSInteger, ADJBackoffStrategyType) { + ADJLongWait = 0, + ADJShortWait = 1, + ADJTestWait = 2, + ADJNoWait = 3, + ADJNoRetry = 4 +}; + +@interface ADJBackoffStrategy : NSObject + +@property (nonatomic, assign) NSUInteger minRetries; +@property (nonatomic, assign) NSTimeInterval secondMultiplier; +@property (nonatomic, assign) NSTimeInterval maxWait; +@property (nonatomic, assign) double minRange; +@property (nonatomic, assign) double maxRange; + +- (id) initWithType:(ADJBackoffStrategyType)strategyType; ++ (ADJBackoffStrategy *)backoffStrategyWithType:(ADJBackoffStrategyType)strategyType; + +@end diff --git a/Adjust/ADJBackoffStrategy.m b/Adjust/ADJBackoffStrategy.m new file mode 100644 index 000000000..57d997258 --- /dev/null +++ b/Adjust/ADJBackoffStrategy.m @@ -0,0 +1,77 @@ +// +// ADJBackoffStrategy.m +// Adjust +// +// Created by Pedro Filipe on 20/04/16. +// Copyright © 2016 adjust GmbH. All rights reserved. +// + +#import "ADJBackoffStrategy.h" + +@implementation ADJBackoffStrategy + ++ (ADJBackoffStrategy *)backoffStrategyWithType:(ADJBackoffStrategyType)strategyType { + return [[ADJBackoffStrategy alloc] initWithType:strategyType]; +} + +- (id) initWithType:(ADJBackoffStrategyType)strategyType { + self = [super init]; + if (self == nil) return nil; + + switch (strategyType) { + case ADJLongWait: + [self saveStrategy:1 + secondMultiplier:120 + maxWait:60*60*24 + minRange:0.5 + maxRange:1.0]; + break; + case ADJShortWait: + [self saveStrategy:1 + secondMultiplier:0.2 + maxWait:60*60 + minRange:0.5 + maxRange:1.0]; + break; + case ADJTestWait: + [self saveStrategy:1 + secondMultiplier:0.2 + maxWait:1 + minRange:0.5 + maxRange:1.0]; + break; + case ADJNoWait: + [self saveStrategy:100 + secondMultiplier:1 + maxWait:1 + minRange:0.5 + maxRange:1.0]; + break; + case ADJNoRetry: + [self saveStrategy:0 + secondMultiplier:100000 + maxWait:100000 + minRange:0.5 + maxRange:1.0]; + break; + default: + break; + } + + return self; +} + +- (void)saveStrategy:(NSInteger)minRetries + secondMultiplier:(NSTimeInterval)secondMultiplier + maxWait:(NSTimeInterval)maxWait + minRange:(double)minRange + maxRange:(double)maxRange +{ + self.minRetries = minRetries; + self.secondMultiplier = secondMultiplier; + self.maxWait = maxWait; + self.minRange = minRange; + self.maxRange = maxRange; +} + +@end diff --git a/Adjust/ADJConfig.h b/Adjust/ADJConfig.h index 56e80bbce..e4edf2ce8 100644 --- a/Adjust/ADJConfig.h +++ b/Adjust/ADJConfig.h @@ -55,6 +55,13 @@ */ - (void)adjustSessionTrackingFailed:(ADJSessionFailure *)sessionFailureResponseData; +/** + * Optional delegate method that gets called when a deeplink is about to be opened by the adjust SDK + * + * @param deeplink The deeplink url that was received by the adjust SDK to be opened + * @return boolean value that indicates whether the deeplink should be opened by the adjust SDK + */ +- (BOOL)adjustDeeplinkResponse:(NSURL *)deeplink; @end @interface ADJConfig : NSObject @@ -113,8 +120,16 @@ * methods like adjustAttributionChanged, adjustTrackingSucceeded or adjustTrackingFailed: */ @property (nonatomic, weak) NSObject *delegate; -@property (nonatomic, assign) BOOL hasDelegate; -@property (nonatomic, assign) BOOL hasAttributionChangedDelegate; + +/** + * Enables sending in the background + * + * @var sendInBackground Enable or disable sending in the background + */ +@property (nonatomic, assign) BOOL sendInBackground; + +@property (nonatomic, assign, readonly) BOOL hasResponseDelegate; +@property (nonatomic, assign, readonly) BOOL hasAttributionChangedDelegate; - (BOOL) isValid; @end diff --git a/Adjust/ADJConfig.m b/Adjust/ADJConfig.m index e7e9af7bc..8d17842c5 100644 --- a/Adjust/ADJConfig.m +++ b/Adjust/ADJConfig.m @@ -43,56 +43,65 @@ - (id) initSelfWithAppToken:(NSString *)appToken // default values self.logLevel = ADJLogLevelInfo; - self.hasDelegate = NO; - self.hasAttributionChangedDelegate = NO; + _hasResponseDelegate = NO; + _hasAttributionChangedDelegate = NO; self.eventBufferingEnabled = NO; return self; } - (void) setDelegate:(NSObject *)delegate { - self.hasDelegate = NO; - self.hasAttributionChangedDelegate = NO; + _hasResponseDelegate = NO; + _hasAttributionChangedDelegate = NO; + BOOL implementsDeeplinkCallback = NO; + + id logger = ADJAdjustFactory.logger; if ([ADJUtil isNull:delegate]) { + [logger warn:@"Delegate is nil"]; _delegate = nil; return; } - id logger = ADJAdjustFactory.logger; - if ([delegate respondsToSelector:@selector(adjustAttributionChanged:)]) { [logger debug:@"Delegate implements adjustAttributionChanged:"]; - self.hasDelegate = YES; - self.hasAttributionChangedDelegate = YES; + _hasResponseDelegate = YES; + _hasAttributionChangedDelegate = YES; } if ([delegate respondsToSelector:@selector(adjustEventTrackingSucceeded:)]) { [logger debug:@"Delegate implements adjustEventTrackingSucceeded:"]; - self.hasDelegate = YES; + _hasResponseDelegate = YES; } if ([delegate respondsToSelector:@selector(adjustEventTrackingFailed:)]) { [logger debug:@"Delegate implements adjustEventTrackingFailed:"]; - self.hasDelegate = YES; + _hasResponseDelegate = YES; } if ([delegate respondsToSelector:@selector(adjustSessionTrackingSucceeded:)]) { [logger debug:@"Delegate implements adjustSessionTrackingSucceeded:"]; - self.hasDelegate = YES; + _hasResponseDelegate = YES; } if ([delegate respondsToSelector:@selector(adjustSessionTrackingFailed:)]) { [logger debug:@"Delegate implements adjustSessionTrackingFailed:"]; - self.hasDelegate = YES; + _hasResponseDelegate = YES; + } + + if ([delegate respondsToSelector:@selector(adjustDeeplinkResponse:)]) { + [logger debug:@"Delegate implements adjustDeeplinkResponse:"]; + + // does not enable hasDelegate flag + implementsDeeplinkCallback = YES; } - if (!self.hasDelegate) { + if (!(self.hasResponseDelegate || implementsDeeplinkCallback)) { [logger error:@"Delegate does not implement any optional method"]; _delegate = nil; return; @@ -145,8 +154,9 @@ -(id)copyWithZone:(NSZone *)zone copy.sdkPrefix = [self.sdkPrefix copyWithZone:zone]; copy.defaultTracker = [self.defaultTracker copyWithZone:zone]; copy.eventBufferingEnabled = self.eventBufferingEnabled; - copy.hasDelegate = self.hasDelegate; - copy.hasAttributionChangedDelegate = self.hasAttributionChangedDelegate; + copy->_hasResponseDelegate = self.hasResponseDelegate; + copy->_hasAttributionChangedDelegate = self.hasAttributionChangedDelegate; + copy.sendInBackground = self.sendInBackground; // adjust delegate not copied } diff --git a/Adjust/ADJDeviceInfo.h b/Adjust/ADJDeviceInfo.h index 758edc92b..baf59492d 100644 --- a/Adjust/ADJDeviceInfo.h +++ b/Adjust/ADJDeviceInfo.h @@ -14,7 +14,6 @@ @property (nonatomic, copy) NSString *fbAttributionId; @property (nonatomic, assign) BOOL trackingEnabled; @property (nonatomic, copy) NSString *vendorId; -@property (nonatomic, copy) NSString *pushToken; @property (nonatomic, copy) NSString *clientSdk; @property (nonatomic, copy) NSString *bundeIdentifier; @property (nonatomic, copy) NSString *bundleVersion; @@ -25,6 +24,8 @@ @property (nonatomic, copy) NSString *systemVersion; @property (nonatomic, copy) NSString *languageCode; @property (nonatomic, copy) NSString *countryCode; +@property (nonatomic, copy) NSString *machineModel; +@property (nonatomic, copy) NSString *cpuSubtype; - (id) initWithSdkPrefix:(NSString *)sdkPrefix; + (ADJDeviceInfo *)deviceInfoWithSdkPrefix:(NSString *)sdkPrefix; diff --git a/Adjust/ADJDeviceInfo.m b/Adjust/ADJDeviceInfo.m index 1a6a1ab41..899a1b367 100644 --- a/Adjust/ADJDeviceInfo.m +++ b/Adjust/ADJDeviceInfo.m @@ -10,9 +10,7 @@ #import "UIDevice+ADJAdditions.h" #import "NSString+ADJAdditions.h" #import "ADJUtil.h" - -static NSString * const kWiFi = @"WIFI"; -static NSString * const kWWAN = @"WWAN"; +#import "ADJSystemProfile.h" @implementation ADJDeviceInfo @@ -42,6 +40,8 @@ - (id) initWithSdkPrefix:(NSString *)sdkPrefix { self.deviceType = device.adjDeviceType; self.deviceName = device.adjDeviceName; self.systemVersion = device.systemVersion; + self.machineModel = [ADJSystemProfile machineModel]; + self.cpuSubtype = [ADJSystemProfile cpuSubtype]; if (sdkPrefix == nil) { self.clientSdk = ADJUtil.clientSdk; @@ -60,7 +60,6 @@ -(id)copyWithZone:(NSZone *)zone copy.fbAttributionId = [self.fbAttributionId copyWithZone:zone]; copy.trackingEnabled = self.trackingEnabled; copy.vendorId = [self.vendorId copyWithZone:zone]; - copy.pushToken = [self.pushToken copyWithZone:zone]; copy.clientSdk = [self.clientSdk copyWithZone:zone]; copy.bundeIdentifier = [self.bundeIdentifier copyWithZone:zone]; copy.bundleVersion = [self.bundleVersion copyWithZone:zone]; @@ -71,8 +70,10 @@ -(id)copyWithZone:(NSZone *)zone copy.systemVersion = [self.systemVersion copyWithZone:zone]; copy.languageCode = [self.languageCode copyWithZone:zone]; copy.countryCode = [self.countryCode copyWithZone:zone]; + copy.machineModel = [self.machineModel copyWithZone:zone]; + copy.cpuSubtype = [self.cpuSubtype copyWithZone:zone]; } - + return copy; } diff --git a/Adjust/ADJPackageBuilder.h b/Adjust/ADJPackageBuilder.h index 5ee89da5a..79e23c57a 100644 --- a/Adjust/ADJPackageBuilder.h +++ b/Adjust/ADJPackageBuilder.h @@ -19,6 +19,8 @@ @property (nonatomic, copy) NSDate *clickTime; @property (nonatomic, retain) NSDictionary *iadDetails; @property (nonatomic, retain) NSDictionary* deeplinkParameters; +@property (nonatomic, copy) NSString *deeplink; +@property (nonatomic, copy) NSString *deviceToken; - (id) initWithDeviceInfo:(ADJDeviceInfo *)deviceInfo activityState:(ADJActivityState *)activityState diff --git a/Adjust/ADJPackageBuilder.m b/Adjust/ADJPackageBuilder.m index ea03816b1..ec6a80e8d 100644 --- a/Adjust/ADJPackageBuilder.m +++ b/Adjust/ADJPackageBuilder.m @@ -100,6 +100,8 @@ - (ADJActivityPackage *)buildClickPackage:(NSString *)clickSource [self parameters:parameters setString:self.attribution.creative forKey:@"creative"]; } [self parameters:parameters setDictionary:self.iadDetails forKey:@"details"]; + [self parameters:parameters setString:self.deeplink forKey:@"deeplink"]; + [self parameters:parameters setString:self.deviceToken forKey:@"push_token"]; ADJActivityPackage *clickPackage = [self defaultActivityPackage]; clickPackage.path = @"/sdk_click"; @@ -166,7 +168,6 @@ - (void) injectDeviceInfo:(ADJDeviceInfo *)deviceInfo intoParameters:parameters]; [self parameters:parameters setString:deviceInfo.fbAttributionId forKey:@"fb_id"]; [self parameters:parameters setInt:deviceInfo.trackingEnabled forKey:@"tracking_enabled"]; - [self parameters:parameters setString:deviceInfo.pushToken forKey:@"push_token"]; [self parameters:parameters setString:deviceInfo.bundeIdentifier forKey:@"bundle_id"]; [self parameters:parameters setString:deviceInfo.bundleVersion forKey:@"app_version"]; [self parameters:parameters setString:deviceInfo.bundleShortVersion forKey:@"app_version_short"]; @@ -176,6 +177,8 @@ - (void) injectDeviceInfo:(ADJDeviceInfo *)deviceInfo [self parameters:parameters setString:deviceInfo.systemVersion forKey:@"os_version"]; [self parameters:parameters setString:deviceInfo.languageCode forKey:@"language"]; [self parameters:parameters setString:deviceInfo.countryCode forKey:@"country"]; + [self parameters:parameters setString:deviceInfo.machineModel forKey:@"hardware_name"]; + [self parameters:parameters setString:deviceInfo.cpuSubtype forKey:@"cpu_type"]; } - (void)injectConfig:(ADJConfig*) adjustConfig @@ -183,7 +186,8 @@ - (void)injectConfig:(ADJConfig*) adjustConfig { [self parameters:parameters setString:adjustConfig.appToken forKey:@"app_token"]; [self parameters:parameters setString:adjustConfig.environment forKey:@"environment"]; - [self parameters:parameters setBool:adjustConfig.hasDelegate forKey:@"needs_response_details"]; + [self parameters:parameters setBool:adjustConfig.hasResponseDelegate forKey:@"needs_response_details"]; + [self parameters:parameters setBool:adjustConfig.eventBufferingEnabled forKey:@"event_buffering_enabled"]; } - (void) injectActivityState:(ADJActivityState *)activityState diff --git a/Adjust/ADJPackageHandler.h b/Adjust/ADJPackageHandler.h index f1ffaba03..88dbbbb4c 100644 --- a/Adjust/ADJPackageHandler.h +++ b/Adjust/ADJPackageHandler.h @@ -15,12 +15,13 @@ @protocol ADJPackageHandler - (id)initWithActivityHandler:(id)activityHandler - startPaused:(BOOL)startPaused; + startsSending:(BOOL)startsSending; - (void)addPackage:(ADJActivityPackage *)package; - (void)sendFirstPackage; - (void)sendNextPackage:(ADJResponseData *)responseData; -- (void)closeFirstPackage:(ADJResponseData *)responseData;; +- (void)closeFirstPackage:(ADJResponseData *)responseData + activityPackage:(ADJActivityPackage *)activityPackage; - (void)pauseSending; - (void)resumeSending; @@ -29,6 +30,6 @@ @interface ADJPackageHandler : NSObject + (id)handlerWithActivityHandler:(id)activityHandler - startPaused:(BOOL)startPaused; + startsSending:(BOOL)startsSending; @end diff --git a/Adjust/ADJPackageHandler.m b/Adjust/ADJPackageHandler.m index 19c2c7797..f6f2c9803 100644 --- a/Adjust/ADJPackageHandler.m +++ b/Adjust/ADJPackageHandler.m @@ -11,6 +11,7 @@ #import "ADJLogger.h" #import "ADJUtil.h" #import "ADJAdjustFactory.h" +#import "ADJBackoffStrategy.h" static NSString * const kPackageQueueFilename = @"AdjustIoPackageQueue"; static const char * const kInternalQueueName = "io.adjust.PackageQueue"; @@ -26,27 +27,30 @@ @interface ADJPackageHandler() @property (nonatomic, retain) id logger; @property (nonatomic, retain) NSMutableArray *packageQueue; @property (nonatomic, assign) BOOL paused; +@property (nonatomic, retain) ADJBackoffStrategy * backoffStrategy; @end - #pragma mark - @implementation ADJPackageHandler + (id)handlerWithActivityHandler:(id)activityHandler - startPaused:(BOOL)startPaused { - return [[ADJPackageHandler alloc] initWithActivityHandler:activityHandler startPaused:startPaused]; + startsSending:(BOOL)startsSending +{ + return [[ADJPackageHandler alloc] initWithActivityHandler:activityHandler startsSending:startsSending]; } - (id)initWithActivityHandler:(id)activityHandler - startPaused:(BOOL)startPaused { + startsSending:(BOOL)startsSending +{ self = [super init]; if (self == nil) return nil; self.internalQueue = dispatch_queue_create(kInternalQueueName, DISPATCH_QUEUE_SERIAL); + self.backoffStrategy = [ADJAdjustFactory packageHandlerBackoffStrategy]; dispatch_async(self.internalQueue, ^{ - [self initInternal:activityHandler startPaused:startPaused]; + [self initInternal:activityHandler startsSending:startsSending]; }); return self; @@ -72,11 +76,28 @@ - (void)sendNextPackage:(ADJResponseData *)responseData{ [self.activityHandler finishedTracking:responseData]; } -- (void)closeFirstPackage:(ADJResponseData *)responseData { - dispatch_semaphore_signal(self.sendingSemaphore); - +- (void)closeFirstPackage:(ADJResponseData *)responseData + activityPackage:(ADJActivityPackage *)activityPackage +{ responseData.willRetry = YES; [self.activityHandler finishedTracking:responseData]; + + if (activityPackage != nil) { + NSInteger retries = [activityPackage increaseRetries]; + + NSTimeInterval waitTime = [ADJUtil waitingTime:retries backoffStrategy:self.backoffStrategy]; + NSString * waitTimeFormatted = [ADJUtil secondsNumberFormat:waitTime]; + + [self.logger verbose:@"Sleeping for %@ seconds before retrying the %d time", waitTimeFormatted, retries]; + + [NSThread sleepForTimeInterval:waitTime]; + } + + [self.logger verbose:@"Package handler can send"]; + dispatch_semaphore_signal(self.sendingSemaphore); + + // Try to send the same package after sleeping + [self sendFirstPackage]; } - (void)pauseSending { @@ -89,10 +110,10 @@ - (void)resumeSending { #pragma mark - internal - (void)initInternal:(id)activityHandler - startPaused:(BOOL)startPaused + startsSending:(BOOL)startsSending { self.activityHandler = activityHandler; - self.paused = startPaused; + self.paused = !startsSending; self.requestHandler = [ADJAdjustFactory requestHandlerForPackageHandler:self]; self.logger = ADJAdjustFactory.logger; self.sendingSemaphore = dispatch_semaphore_create(1); @@ -100,11 +121,7 @@ - (void)initInternal:(id)activityHandler } - (void)addInternal:(ADJActivityPackage *)newPackage { - if (newPackage.activityKind == ADJActivityKindClick && [self.packageQueue count] > 0) { - [self.packageQueue insertObject:newPackage atIndex:1]; - } else { - [self.packageQueue addObject:newPackage]; - } + [self.packageQueue addObject:newPackage]; [self.logger debug:@"Added package %d (%@)", self.packageQueue.count, newPackage]; [self.logger verbose:@"%@", newPackage.extendedString]; @@ -112,7 +129,8 @@ - (void)addInternal:(ADJActivityPackage *)newPackage { } - (void)sendFirstInternal { - if (self.packageQueue.count == 0) return; + NSUInteger queueSize = self.packageQueue.count; + if (queueSize == 0) return; if (self.paused) { [self.logger debug:@"Package handler is paused"]; @@ -131,7 +149,8 @@ - (void)sendFirstInternal { return; } - [self.requestHandler sendPackage:activityPackage]; + [self.requestHandler sendPackage:activityPackage + queueSize:queueSize - 1]; } - (void)sendNextInternal { diff --git a/Adjust/ADJRequestHandler.h b/Adjust/ADJRequestHandler.h index 99f0c2f34..d148c562a 100644 --- a/Adjust/ADJRequestHandler.h +++ b/Adjust/ADJRequestHandler.h @@ -13,7 +13,8 @@ - (id)initWithPackageHandler:(id) packageHandler; -- (void)sendPackage:(ADJActivityPackage *)activityPackage; +- (void)sendPackage:(ADJActivityPackage *)activityPackage + queueSize:(NSUInteger)queueSize; @end diff --git a/Adjust/ADJRequestHandler.m b/Adjust/ADJRequestHandler.m index ee12a30f1..72f9c7e42 100644 --- a/Adjust/ADJRequestHandler.m +++ b/Adjust/ADJRequestHandler.m @@ -14,8 +14,6 @@ #import "ADJActivityKind.h" static const char * const kInternalQueueName = "io.adjust.RequestQueue"; -static const double kRequestTimeout = 60; // 60 seconds - #pragma mark - private @interface ADJRequestHandler() @@ -47,47 +45,33 @@ - (id)initWithPackageHandler:(id) packageHandler { return self; } -- (void)sendPackage:(ADJActivityPackage *)activityPackage { +- (void)sendPackage:(ADJActivityPackage *)activityPackage + queueSize:(NSUInteger)queueSize +{ dispatch_async(self.internalQueue, ^{ - [self sendInternal:activityPackage]; + [self sendInternal:activityPackage queueSize:queueSize]; }); } #pragma mark - internal -- (void)sendInternal:(ADJActivityPackage *)package{ - - [ADJUtil sendRequest:[self requestForPackage:package] - prefixErrorMessage:package.failureMessage - suffixErrorMessage:@"Will retry later" - activityPackage:package - responseDataHandler:^(ADJResponseData * responseData) { - if (responseData.jsonResponse == nil) { - [self.packageHandler closeFirstPackage:responseData]; - return; - } - - [self.packageHandler sendNextPackage:responseData]; +- (void)sendInternal:(ADJActivityPackage *)package + queueSize:(NSUInteger)queueSize +{ + + [ADJUtil sendPostRequest:self.baseUrl + queueSize:queueSize + prefixErrorMessage:package.failureMessage + suffixErrorMessage:@"Will retry later" + activityPackage:package + responseDataHandler:^(ADJResponseData * responseData) + { + if (responseData.jsonResponse == nil) { + [self.packageHandler closeFirstPackage:responseData activityPackage:package]; + return; + } + + [self.packageHandler sendNextPackage:responseData]; }]; } -#pragma mark - private -- (NSMutableURLRequest *)requestForPackage:(ADJActivityPackage *)package { - NSURL *url = [NSURL URLWithString:package.path relativeToURL:self.baseUrl]; - NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; - request.timeoutInterval = kRequestTimeout; - request.HTTPMethod = @"POST"; - - [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; - [request setValue:package.clientSdk forHTTPHeaderField:@"Client-Sdk"]; - [request setHTTPBody:[self bodyForParameters:package.parameters]]; - - return request; -} - -- (NSData *)bodyForParameters:(NSDictionary *)parameters { - NSString *bodyString = [ADJUtil queryString:parameters]; - NSData *body = [NSData dataWithBytes:bodyString.UTF8String length:bodyString.length]; - return body; -} - @end diff --git a/Adjust/ADJResponseData.h b/Adjust/ADJResponseData.h index 9e9453a54..1de93766c 100644 --- a/Adjust/ADJResponseData.h +++ b/Adjust/ADJResponseData.h @@ -68,4 +68,4 @@ @interface ADJUnknowResponseData : ADJResponseData -@end \ No newline at end of file +@end diff --git a/Adjust/ADJSdkClickHandler.h b/Adjust/ADJSdkClickHandler.h new file mode 100644 index 000000000..22979602a --- /dev/null +++ b/Adjust/ADJSdkClickHandler.h @@ -0,0 +1,26 @@ +// +// ADJSdkClickHandler.h +// Adjust +// +// Created by Pedro Filipe on 21/04/16. +// Copyright © 2016 adjust GmbH. All rights reserved. +// + +#import +#import "ADJActivityPackage.h" + +@protocol ADJSdkClickHandler + +- (id)initWithStartsSending:(BOOL)startsSending; + +- (void)pauseSending; +- (void)resumeSending; +- (void)sendSdkClick:(ADJActivityPackage *)sdkClickPackage; + +@end + +@interface ADJSdkClickHandler : NSObject + ++ (id)handlerWithStartsSending:(BOOL)startsSending; + +@end diff --git a/Adjust/ADJSdkClickHandler.m b/Adjust/ADJSdkClickHandler.m new file mode 100644 index 000000000..e968b3197 --- /dev/null +++ b/Adjust/ADJSdkClickHandler.m @@ -0,0 +1,136 @@ +// +// ADJSdkClickHandler.m +// Adjust +// +// Created by Pedro Filipe on 21/04/16. +// Copyright © 2016 adjust GmbH. All rights reserved. +// + +#import "ADJSdkClickHandler.h" +#import "ADJLogger.h" +#import "ADJAdjustFactory.h" +#import "ADJBackoffStrategy.h" +#import "ADJUtil.h" + +static const char * const kInternalQueueName = "com.adjust.SdkClickQueue"; + +#pragma mark - private +@interface ADJSdkClickHandler() + +@property (nonatomic) dispatch_queue_t internalQueue; +@property (nonatomic, retain) id logger; +@property (nonatomic, retain) ADJBackoffStrategy * backoffStrategy; +@property (nonatomic, assign) BOOL paused; +@property (nonatomic, retain) NSMutableArray *packageQueue; +@property (nonatomic, retain) NSURL *baseUrl; + +@end + +@implementation ADJSdkClickHandler + ++ (id)handlerWithStartsSending:(BOOL)startsSending +{ + return [[ADJSdkClickHandler alloc] initWithStartsSending:startsSending]; +} + +- (id)initWithStartsSending:(BOOL)startsSending +{ + self = [super init]; + if (self == nil) return nil; + + self.internalQueue = dispatch_queue_create(kInternalQueueName, DISPATCH_QUEUE_SERIAL); + + dispatch_async(self.internalQueue, ^{ + [self initInternal:startsSending]; + }); + + return self; +} + +- (void)pauseSending { + self.paused = YES; +} + +- (void)resumeSending { + self.paused = NO; + + [self sendNextSdkClick]; +} + +- (void)sendSdkClick:(ADJActivityPackage *)sdkClickPackage { + dispatch_async(self.internalQueue, ^{ + [self sendSdkClickInternal:sdkClickPackage]; + }); +} + +- (void)sendNextSdkClick { + dispatch_async(self.internalQueue, ^{ + [self sendNextSdkClickInternal]; + }); +} + +#pragma mark - internal +- (void)initInternal:(BOOL)startsSending +{ + self.paused = !startsSending; + self.logger = ADJAdjustFactory.logger; + self.backoffStrategy = [ADJAdjustFactory sdkClickHandlerBackoffStrategy]; + self.packageQueue = [NSMutableArray array]; + self.baseUrl = [NSURL URLWithString:ADJUtil.baseUrl]; +} + +- (void)sendSdkClickInternal:(ADJActivityPackage *)sdkClickPackage { + [self.packageQueue addObject:sdkClickPackage]; + + [self.logger debug:@"Added sdk_click %d", self.packageQueue.count]; + [self.logger verbose:@"%@", sdkClickPackage.extendedString]; + + [self sendNextSdkClick]; +} + +- (void)sendNextSdkClickInternal { + if (self.paused) return; + NSUInteger queueSize = self.packageQueue.count; + if (queueSize == 0) return; + + ADJActivityPackage *sdkClickPackage = [self.packageQueue objectAtIndex:0]; + + if (![sdkClickPackage isKindOfClass:[ADJActivityPackage class]]) { + [self.logger error:@"Failed to read sdk_click package"]; + + [self.packageQueue removeObjectAtIndex:0]; + [self sendNextSdkClick]; + + return; + } + + NSInteger retries = [sdkClickPackage retries]; + if (retries > 0) { + NSTimeInterval waitTime = [ADJUtil waitingTime:retries backoffStrategy:self.backoffStrategy]; + NSString * waitTimeFormatted = [ADJUtil secondsNumberFormat:waitTime]; + + [self.logger verbose:@"Sleeping for %@ seconds before retrying sdk_click for the %d time", waitTimeFormatted, retries]; + + [NSThread sleepForTimeInterval:waitTime]; + } + + [ADJUtil sendPostRequest:self.baseUrl + queueSize:queueSize - 1 + prefixErrorMessage:sdkClickPackage.failureMessage + suffixErrorMessage:@"Will retry later" + activityPackage:sdkClickPackage + responseDataHandler:^(ADJResponseData * responseData) + { + if (responseData.jsonResponse == nil) { + NSInteger retries = [sdkClickPackage increaseRetries]; + [self.logger error:@"Retrying sdk_click package for the %d time", retries]; + + [self sendSdkClick:sdkClickPackage]; + } + }]; + + [self.packageQueue removeObjectAtIndex:0]; + [self sendNextSdkClick]; +} + +@end diff --git a/Adjust/ADJSystemProfile.h b/Adjust/ADJSystemProfile.h new file mode 100644 index 000000000..b0779a896 --- /dev/null +++ b/Adjust/ADJSystemProfile.h @@ -0,0 +1,37 @@ +/* + * Copyright 2008-2014, Torsten Curdt + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// original at https://github.com/tcurdt/feedbackreporter/blob/master/Sources/Main/FRSystemProfile.h + +#import + +@interface ADJSystemProfile : NSObject + ++ (BOOL) is64bit; ++ (NSString*) cpuFamily; ++ (NSString*) osVersion; ++ (int) cpuCount; ++ (NSString*) machineArch; ++ (NSString*) machineModel; ++ (NSString*) cpuBrand; ++ (NSString*) cpuFeatures; ++ (NSString*) cpuVendor; ++ (NSString*) appleLanguage; ++ (long long) cpuSpeed; ++ (long long) ramsize; ++ (NSString*) cpuType; ++ (NSString*) cpuSubtype; + +@end diff --git a/Adjust/ADJSystemProfile.m b/Adjust/ADJSystemProfile.m new file mode 100644 index 000000000..f3baea210 --- /dev/null +++ b/Adjust/ADJSystemProfile.m @@ -0,0 +1,566 @@ +/* + * Copyright 2008-2014, Torsten Curdt + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +// original at https://github.com/tcurdt/feedbackreporter/blob/master/Sources/Main/FRSystemProfile.m + +#import "ADJSystemProfile.h" +#import +#import +#import + +@implementation ADJSystemProfile + ++ (BOOL) is64bit +{ + int error = 0; + int value = 0; + size_t length = sizeof(value); + + error = sysctlbyname("hw.cpu64bit_capable", &value, &length, NULL, 0); + + if(error != 0) { + error = sysctlbyname("hw.optional.x86_64", &value, &length, NULL, 0); //x86 specific + } + + if(error != 0) { + error = sysctlbyname("hw.optional.64bitops", &value, &length, NULL, 0); //PPC specific + } + + if (error != 0) { + return NO; + } + + return value == 1; +} + ++ (NSString*) cpuFamily +{ + int cpufamily = -1; + size_t length = sizeof(cpufamily); + int error = sysctlbyname("hw.cpufamily", &cpufamily, &length, NULL, 0); + + if (error != 0) { + NSLog(@"Failed to obtain CPU family (%d)", error); + return nil; + } + switch (cpufamily) + { + case CPUFAMILY_UNKNOWN: + return @"CPUFAMILY_UNKNOWN"; + case CPUFAMILY_POWERPC_G3: + return @"CPUFAMILY_POWERPC_G3"; + case CPUFAMILY_POWERPC_G4: + return @"CPUFAMILY_POWERPC_G4"; + case CPUFAMILY_POWERPC_G5: + return @"CPUFAMILY_POWERPC_G5"; + case CPUFAMILY_INTEL_6_13: + return @"CPUFAMILY_INTEL_6_13"; + case CPUFAMILY_INTEL_YONAH: + return @"CPUFAMILY_INTEL_YONAH"; + case CPUFAMILY_INTEL_MEROM: + return @"CPUFAMILY_INTEL_MEROM"; + case CPUFAMILY_INTEL_PENRYN: + return @"CPUFAMILY_INTEL_PENRYN"; + case CPUFAMILY_INTEL_NEHALEM: + return @"CPUFAMILY_INTEL_NEHALEM"; + case CPUFAMILY_INTEL_WESTMERE: + return @"CPUFAMILY_INTEL_WESTMERE"; + case CPUFAMILY_INTEL_SANDYBRIDGE: + return @"CPUFAMILY_INTEL_SANDYBRIDGE"; + case CPUFAMILY_INTEL_IVYBRIDGE: + return @"CPUFAMILY_INTEL_IVYBRIDGE"; + case CPUFAMILY_INTEL_HASWELL: + return @"CPUFAMILY_INTEL_HASWELL"; + case CPUFAMILY_INTEL_BROADWELL: + return @"CPUFAMILY_INTEL_BROADWELL"; + case CPUFAMILY_INTEL_SKYLAKE: + return @"CPUFAMILY_INTEL_SKYLAKE"; + case CPUFAMILY_ARM_9: + return @"CPUFAMILY_ARM_9"; + case CPUFAMILY_ARM_11: + return @"CPUFAMILY_ARM_11"; + case CPUFAMILY_ARM_XSCALE: + return @"CPUFAMILY_ARM_XSCALE"; + case CPUFAMILY_ARM_12: + return @"CPUFAMILY_ARM_12"; + case CPUFAMILY_ARM_13: + return @"CPUFAMILY_ARM_13"; + case CPUFAMILY_ARM_14: + return @"CPUFAMILY_ARM_14"; + case CPUFAMILY_ARM_15: + return @"CPUFAMILY_ARM_15"; + case CPUFAMILY_ARM_SWIFT: + return @"CPUFAMILY_ARM_SWIFT"; + case CPUFAMILY_ARM_CYCLONE: + return @"CPUFAMILY_ARM_CYCLONE"; + case CPUFAMILY_ARM_TYPHOON: + return @"CPUFAMILY_ARM_TYPHOON"; + } + NSString * unknowCpuFamily = [NSString stringWithFormat:@"Unknown CPU family %d", cpufamily]; + NSLog(@"%@", unknowCpuFamily); + return unknowCpuFamily; +} + ++ (NSString*) osVersion +{ + NSProcessInfo *info = [NSProcessInfo processInfo]; + if (info == nil) { + return nil; + } + NSString *version = [info operatingSystemVersionString]; + + if ([version hasPrefix:@"Version "]) { + version = [version substringFromIndex:8]; + } + + return version; +} + ++ (int) cpuCount +{ + int error = 0; + int value = 0; + size_t length = sizeof(value); + error = sysctlbyname("hw.ncpu", &value, &length, NULL, 0); + + if (error != 0) { + NSLog(@"Failed to obtain CPU count"); + return 1; + } + + return value; +} + ++ (NSString*) machineArch +{ + return [ADJSystemProfile readSysctlbString:"hw.machinearch" errorLog:@"Failed to obtain machine arch"]; +} + ++ (NSString*) machineModel +{ + return [ADJSystemProfile readSysctlbString:"hw.model" errorLog:@"Failed to obtain machine model"]; +} + ++ (NSString*) cpuBrand +{ + return [ADJSystemProfile readSysctlbString:"machdep.cpu.brand_string" errorLog:@"Failed to obtain CPU brand"]; +} + ++ (NSString*) cpuFeatures +{ + return [ADJSystemProfile readSysctlbString:"machdep.cpu.features" errorLog:@"Failed to obtain CPU features"]; +} + ++ (NSString*) cpuVendor +{ + return [ADJSystemProfile readSysctlbString:"machdep.cpu.vendor" errorLog:@"Failed to obtain CPU vendor"]; +} + ++ (NSString*) appleLanguage +{ + NSUserDefaults *defs = [NSUserDefaults standardUserDefaults]; + NSArray *languages = [defs objectForKey:@"AppleLanguages"]; + + if ([languages count] == 0) { + NSLog(@"Failed to obtain preferred language"); + return nil; + } + + return [languages objectAtIndex:0]; +} + ++ (long long) cpuSpeed +{ + long long result = 0; + + int error = 0; + + int64_t hertz = 0; + size_t size = sizeof(hertz); + int mib[2] = {CTL_HW, HW_CPU_FREQ}; + + error = sysctl(mib, 2, &hertz, &size, NULL, 0); + + if (error) { + NSLog(@"Failed to obtain CPU speed"); + return -1; + } + + result = (long long)(hertz/1000000); // Convert to MHz + + return result; +} + ++ (long long) ramsize +{ + long long result = 0; + + int error = 0; + int64_t value = 0; + size_t length = sizeof(value); + + error = sysctlbyname("hw.memsize", &value, &length, NULL, 0); + if (error) { + NSLog(@"Failed to obtain RAM size"); + return -1; + } + const int64_t kBytesPerMebibyte = 1024*1024; + result = (long long)(value/kBytesPerMebibyte); + + return result; +} + + ++ (NSString*) cpuType +{ + int error = 0; + + int cputype = -1; + size_t length = sizeof(cputype); + error = sysctlbyname("hw.cputype", &cputype, &length, NULL, 0); + + if (error != 0) { + NSLog(@"Failed to obtain CPU type"); + return nil; + } + + NSString * cpuTypeString = [ADJSystemProfile readCpuTypeSubtype:cputype readSubType:NO cpusubtype:0]; + + if (cpuTypeString != nil) { + return cpuTypeString; + } + + NSString * unknowCpuType = [NSString stringWithFormat:@"Unknown CPU type %d", cputype]; + NSLog(@"%@", unknowCpuType); + return unknowCpuType; +} + ++ (NSString*) cpuSubtype +{ + int error = 0; + + int cputype = -1; + size_t length = sizeof(cputype); + error = sysctlbyname("hw.cputype", &cputype, &length, NULL, 0); + + if (error != 0) { + NSLog(@"Failed to obtain CPU type"); + return nil; + } + + int cpuSubtype = -1; + length = sizeof(cpuSubtype); + error = sysctlbyname("hw.cpusubtype", &cpuSubtype, &length, NULL, 0); + + if (error != 0) { + NSLog(@"Failed to obtain CPU subtype"); + return nil; + } + + + NSString * cpuSubtypeString = [ADJSystemProfile readCpuTypeSubtype:cputype readSubType:YES cpusubtype:cpuSubtype]; + + if (cpuSubtypeString != nil) { + return cpuSubtypeString; + } + + NSString * unknowCpuSubtype = [NSString stringWithFormat:@"Unknown CPU subtype %d", cpuSubtype]; + NSLog(@"%@", unknowCpuSubtype); + return unknowCpuSubtype; +} + + + ++ (NSString*) readCpuTypeSubtype:(int)cputype + readSubType:(BOOL)readSubType + cpusubtype:(int)cpusubtype +{ + switch (cputype) + { + case CPU_TYPE_ANY: + if (!readSubType) return @"CPU_TYPE_ANY"; + switch (cpusubtype) + { + case CPU_SUBTYPE_MULTIPLE: + return @"CPU_SUBTYPE_MULTIPLE"; + case CPU_SUBTYPE_LITTLE_ENDIAN: + return @"CPU_SUBTYPE_LITTLE_ENDIAN"; + case CPU_SUBTYPE_BIG_ENDIAN: + return @"CPU_SUBTYPE_BIG_ENDIAN"; + } + break; + case CPU_TYPE_VAX: + if (!readSubType) return @"CPU_TYPE_VAX"; + switch (cpusubtype) + { + case CPU_SUBTYPE_VAX_ALL: + return @"CPU_SUBTYPE_VAX_ALL"; + case CPU_SUBTYPE_VAX780: + return @"CPU_SUBTYPE_VAX780"; + case CPU_SUBTYPE_VAX785: + return @"CPU_SUBTYPE_VAX785"; + case CPU_SUBTYPE_VAX750: + return @"CPU_SUBTYPE_VAX750"; + case CPU_SUBTYPE_VAX730: + return @"CPU_SUBTYPE_VAX730"; + case CPU_SUBTYPE_UVAXI: + return @"CPU_SUBTYPE_UVAXI"; + case CPU_SUBTYPE_UVAXII: + return @"CPU_SUBTYPE_UVAXII"; + case CPU_SUBTYPE_VAX8200: + return @"CPU_SUBTYPE_VAX8200"; + case CPU_SUBTYPE_VAX8500: + return @"CPU_SUBTYPE_VAX8500"; + case CPU_SUBTYPE_VAX8600: + return @"CPU_SUBTYPE_VAX8600"; + case CPU_SUBTYPE_VAX8650: + return @"CPU_SUBTYPE_VAX8650"; + case CPU_SUBTYPE_VAX8800: + return @"CPU_SUBTYPE_VAX8800"; + case CPU_SUBTYPE_UVAXIII: + return @"CPU_SUBTYPE_UVAXIII"; + } + break; + case CPU_TYPE_MC680x0: + if (!readSubType) return @"CPU_TYPE_MC680x0"; + switch (cpusubtype) + { + case CPU_SUBTYPE_MC680x0_ALL: + return @"CPU_SUBTYPE_MC680x0_ALL"; + case CPU_SUBTYPE_MC68040: + return @"CPU_SUBTYPE_MC68040"; + case CPU_SUBTYPE_MC68030_ONLY: + return @"CPU_SUBTYPE_MC68030_ONLY"; + } + break; + case CPU_TYPE_X86_64: + if (!readSubType) return @"CPU_TYPE_X86_64"; + switch (cpusubtype) + { + case CPU_SUBTYPE_X86_64_ALL: + return @"CPU_SUBTYPE_X86_64_ALL"; + case CPU_SUBTYPE_X86_ARCH1: + return @"CPU_SUBTYPE_X86_ARCH1"; + case CPU_SUBTYPE_X86_64_H: + return @"CPU_SUBTYPE_X86_64_H"; + } + break; + case CPU_TYPE_X86: + if (!readSubType) return @"CPU_TYPE_X86"; + switch (cpusubtype) { + case CPU_SUBTYPE_386: + return @"CPU_SUBTYPE_386"; + case CPU_SUBTYPE_486: + return @"CPU_SUBTYPE_486"; + case CPU_SUBTYPE_486SX: + return @"CPU_SUBTYPE_486SX"; + case CPU_SUBTYPE_PENT: + return @"CPU_SUBTYPE_PENT"; + case CPU_SUBTYPE_PENTPRO: + return @"CPU_SUBTYPE_PENTPRO"; + case CPU_SUBTYPE_PENTII_M3: + return @"CPU_SUBTYPE_PENTII_M3"; + case CPU_SUBTYPE_PENTII_M5: + return @"CPU_SUBTYPE_PENTII_M5"; + case CPU_SUBTYPE_CELERON: + return @"CPU_SUBTYPE_CELERON"; + case CPU_SUBTYPE_CELERON_MOBILE: + return @"CPU_SUBTYPE_CELERON_MOBILE"; + case CPU_SUBTYPE_PENTIUM_3: + return @"CPU_SUBTYPE_PENTIUM_3"; + case CPU_SUBTYPE_PENTIUM_3_M: + return @"CPU_SUBTYPE_PENTIUM_3_M"; + case CPU_SUBTYPE_PENTIUM_3_XEON: + return @"CPU_SUBTYPE_PENTIUM_3_XEON"; + case CPU_SUBTYPE_PENTIUM_M: + return @"CPU_SUBTYPE_PENTIUM_M"; + case CPU_SUBTYPE_PENTIUM_4: + return @"CPU_SUBTYPE_PENTIUM_4"; + case CPU_SUBTYPE_PENTIUM_4_M: + return @"CPU_SUBTYPE_PENTIUM_4_M"; + case CPU_SUBTYPE_ITANIUM: + return @"CPU_SUBTYPE_ITANIUM"; + case CPU_SUBTYPE_ITANIUM_2: + return @"CPU_SUBTYPE_ITANIUM_2"; + case CPU_SUBTYPE_XEON: + return @"CPU_SUBTYPE_XEON"; + case CPU_SUBTYPE_XEON_MP: + return @"CPU_SUBTYPE_XEON_MP"; + } + break; + case CPU_TYPE_MC98000: + if (!readSubType) return @"CPU_TYPE_MC98000"; + switch (cpusubtype) + { + case CPU_SUBTYPE_MC98000_ALL: + return @"CPU_SUBTYPE_MC98000_ALL"; + case CPU_SUBTYPE_MC98601: + return @"CPU_SUBTYPE_MC98601"; + } + break; + case CPU_TYPE_HPPA: + if (!readSubType) return @"CPU_TYPE_HPPA"; + switch (cpusubtype) + { + case CPU_SUBTYPE_HPPA_7100: + return @"CPU_SUBTYPE_HPPA_7100"; + case CPU_SUBTYPE_HPPA_7100LC: + return @"CPU_SUBTYPE_HPPA_7100LC"; + } + break; + case CPU_TYPE_ARM64: + if (!readSubType) return @"CPU_TYPE_ARM64"; + switch (cpusubtype) + { + case CPU_SUBTYPE_ARM64_ALL: + return @"CPU_SUBTYPE_ARM64_ALL"; + case CPU_SUBTYPE_ARM64_V8: + return @"CPU_SUBTYPE_ARM64_V8"; + } + break; + case CPU_TYPE_ARM: + if (!readSubType) return @"CPU_TYPE_ARM"; + switch (cpusubtype) + { + case CPU_SUBTYPE_ARM_ALL: + return @"CPU_SUBTYPE_ARM_ALL"; + case CPU_SUBTYPE_ARM_V4T: + return @"CPU_SUBTYPE_ARM_V4T"; + case CPU_SUBTYPE_ARM_V6: + return @"CPU_SUBTYPE_ARM_V6"; + case CPU_SUBTYPE_ARM_V5TEJ: + return @"CPU_SUBTYPE_ARM_V5TEJ"; + case CPU_SUBTYPE_ARM_XSCALE: + return @"CPU_SUBTYPE_ARM_XSCALE"; + case CPU_SUBTYPE_ARM_V7: + return @"CPU_SUBTYPE_ARM_V7"; + case CPU_SUBTYPE_ARM_V7F: + return @"CPU_SUBTYPE_ARM_V7F"; + case CPU_SUBTYPE_ARM_V7S: + return @"CPU_SUBTYPE_ARM_V7S"; + case CPU_SUBTYPE_ARM_V7K: + return @"CPU_SUBTYPE_ARM_V7K"; + case CPU_SUBTYPE_ARM_V6M: + return @"CPU_SUBTYPE_ARM_V6M"; + case CPU_SUBTYPE_ARM_V7M: + return @"CPU_SUBTYPE_ARM_V7M"; + case CPU_SUBTYPE_ARM_V7EM: + return @"CPU_SUBTYPE_ARM_V7EM"; + case CPU_SUBTYPE_ARM_V8: + return @"CPU_SUBTYPE_ARM_V8"; + } + break; + case CPU_TYPE_MC88000: + if (!readSubType) return @"CPU_TYPE_MC88000"; + switch (cpusubtype) + { + case CPU_SUBTYPE_MC88000_ALL: + return @"CPU_SUBTYPE_MC88000_ALL"; + case CPU_SUBTYPE_MC88100: + return @"CPU_SUBTYPE_MC88100"; + case CPU_SUBTYPE_MC88110: + return @"CPU_SUBTYPE_MC88110"; + } + break; + case CPU_TYPE_SPARC: + if (!readSubType) return @"CPU_TYPE_SPARC"; + switch (cpusubtype) + { + case CPU_SUBTYPE_SPARC_ALL: + return @"CPU_SUBTYPE_SPARC_ALL"; + } + break; + case CPU_TYPE_I860: + if (!readSubType) return @"CPU_TYPE_I860"; + switch (cpusubtype) + { + case CPU_SUBTYPE_I860_ALL: + return @"CPU_SUBTYPE_I860_ALL"; + case CPU_SUBTYPE_I860_860: + return @"CPU_SUBTYPE_I860_860"; + } + break; + case CPU_TYPE_POWERPC64: + if (!readSubType) return @"CPU_TYPE_POWERPC64"; + break; + case CPU_TYPE_POWERPC: + if (!readSubType) return @"CPU_TYPE_POWERPC"; + switch (cpusubtype) + { + case CPU_SUBTYPE_POWERPC_ALL: + return @"CPU_SUBTYPE_POWERPC_ALL"; + case CPU_SUBTYPE_POWERPC_601: + return @"CPU_SUBTYPE_POWERPC_601"; + case CPU_SUBTYPE_POWERPC_602: + return @"CPU_SUBTYPE_POWERPC_602"; + case CPU_SUBTYPE_POWERPC_603: + return @"CPU_SUBTYPE_POWERPC_603"; + case CPU_SUBTYPE_POWERPC_603e: + return @"CPU_SUBTYPE_POWERPC_603e"; + case CPU_SUBTYPE_POWERPC_603ev: + return @"CPU_SUBTYPE_POWERPC_603ev"; + case CPU_SUBTYPE_POWERPC_604: + return @"CPU_SUBTYPE_POWERPC_604"; + case CPU_SUBTYPE_POWERPC_604e: + return @"CPU_SUBTYPE_POWERPC_604e"; + case CPU_SUBTYPE_POWERPC_620: + return @"CPU_SUBTYPE_POWERPC_620"; + case CPU_SUBTYPE_POWERPC_750: + return @"CPU_SUBTYPE_POWERPC_750"; + case CPU_SUBTYPE_POWERPC_7400: + return @"CPU_SUBTYPE_POWERPC_7400"; + case CPU_SUBTYPE_POWERPC_7450: + return @"CPU_SUBTYPE_POWERPC_7450"; + case CPU_SUBTYPE_POWERPC_970: + return @"CPU_SUBTYPE_POWERPC_970"; + } + break; + } + + return nil; +} + ++ (NSString*) readSysctlbString:(const char *)name + errorLog:(NSString*)errorLog +{ + int error = 0; + size_t length = 0; + error = sysctlbyname(name, NULL, &length, NULL, 0); + + if (error != 0) { + NSLog(@"%@", errorLog); + return nil; + } + + char *p = malloc(sizeof(char) * length); + if (p) { + error = sysctlbyname(name, p, &length, NULL, 0); + } + + if (error != 0) { + NSLog(@"%@", errorLog); + free(p); + return nil; + } + + NSString * result = [NSString stringWithUTF8String:p]; + + free(p); + + return result; + +} + +@end diff --git a/Adjust/ADJTimerCycle.h b/Adjust/ADJTimerCycle.h index c671ebea1..dbd7e652e 100644 --- a/Adjust/ADJTimerCycle.h +++ b/Adjust/ADJTimerCycle.h @@ -10,17 +10,17 @@ @interface ADJTimerCycle : NSObject -@property (nonatomic, assign) NSTimeInterval startTime; - + (ADJTimerCycle *)timerWithBlock:(dispatch_block_t)block - queue:(dispatch_queue_t)queue - startTime:(NSTimeInterval)startTime - intervalTime:(NSTimeInterval)intervalTime; + queue:(dispatch_queue_t)queue + startTime:(NSTimeInterval)startTime + intervalTime:(NSTimeInterval)intervalTime + name:(NSString*)name; - (id)initBlock:(dispatch_block_t)block queue:(dispatch_queue_t)queue startTime:(NSTimeInterval)startTime - intervalTime:(NSTimeInterval)intervalTime; + intervalTime:(NSTimeInterval)intervalTime + name:(NSString*)name; - (void)resume; - (void)suspend; diff --git a/Adjust/ADJTimerCycle.m b/Adjust/ADJTimerCycle.m index d115c7cc1..48dfca9d8 100644 --- a/Adjust/ADJTimerCycle.m +++ b/Adjust/ADJTimerCycle.m @@ -7,6 +7,9 @@ // #import "ADJTimerCycle.h" +#import "ADJLogger.h" +#import "ADJAdjustFactory.h" +#import "ADJUtil.h" static const uint64_t kTimerLeeway = 1 * NSEC_PER_SEC; // 1 second @@ -15,6 +18,8 @@ @interface ADJTimerCycle() @property (nonatomic) dispatch_source_t source; @property (nonatomic, assign) BOOL suspended; +@property (nonatomic, retain) id logger; +@property (nonatomic, copy) NSString *name; @end @@ -22,45 +27,67 @@ @interface ADJTimerCycle() @implementation ADJTimerCycle + (ADJTimerCycle *)timerWithBlock:(dispatch_block_t)block - queue:(dispatch_queue_t)queue - startTime:(NSTimeInterval)startTime - intervalTime:(NSTimeInterval)intervalTime + queue:(dispatch_queue_t)queue + startTime:(NSTimeInterval)startTime + intervalTime:(NSTimeInterval)intervalTime + name:(NSString*)name { - return [[ADJTimerCycle alloc] initBlock:block queue:queue startTime:startTime intervalTime:intervalTime]; + return [[ADJTimerCycle alloc] initBlock:block queue:queue startTime:startTime intervalTime:intervalTime name:name]; } - (id)initBlock:(dispatch_block_t)block queue:(dispatch_queue_t)queue startTime:(NSTimeInterval)startTime intervalTime:(NSTimeInterval)intervalTime + name:(NSString*)name + { self = [super init]; if (self == nil) return nil; self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); + self.logger = ADJAdjustFactory.logger; + self.name = name; dispatch_source_set_timer(self.source, dispatch_walltime(NULL, startTime * NSEC_PER_SEC), intervalTime * NSEC_PER_SEC, kTimerLeeway); - dispatch_source_set_event_handler(self.source, block); + dispatch_source_set_event_handler(self.source, + ^{ [self.logger verbose:@"%@ fired", self.name]; + block(); + }); self.suspended = YES; + NSString * startTimeFormatted = [ADJUtil secondsNumberFormat:startTime]; + NSString * intervalTimeFormatted = [ADJUtil secondsNumberFormat:intervalTime]; + + [self.logger verbose:@"%@ fires after %@ seconds of starting and cycles every %@ seconds", self.name, startTimeFormatted, intervalTimeFormatted]; + return self; } - (void)resume { - if (!self.suspended) return; + if (!self.suspended) { + [self.logger verbose:@"%@ is already started", self.name]; + return; + } + + [self.logger verbose:@"%@ starting", self.name]; dispatch_resume(self.source); self.suspended = NO; } - (void)suspend { - if (self.suspended) return; + if (self.suspended) { + [self.logger verbose:@"%@ is already suspended", self.name]; + return; + } + [self.logger verbose:@"%@ suspended", self.name]; dispatch_suspend(self.source); self.suspended = YES; } diff --git a/Adjust/ADJTimerOnce.h b/Adjust/ADJTimerOnce.h index b9d77169b..b75a74131 100644 --- a/Adjust/ADJTimerOnce.h +++ b/Adjust/ADJTimerOnce.h @@ -11,14 +11,15 @@ @interface ADJTimerOnce : NSObject -@property (nonatomic, assign) NSTimeInterval startTime; - + (ADJTimerOnce *)timerWithBlock:(dispatch_block_t)block - queue:(dispatch_queue_t)queue; + queue:(dispatch_queue_t)queue + name:(NSString*)name; - (id)initBlock:(dispatch_block_t)block - queue:(dispatch_queue_t)queue; + queue:(dispatch_queue_t)queue + name:(NSString*)name; - (void)startIn:(NSTimeInterval)startIn; - (NSTimeInterval)fireIn; +- (void)cancel; @end diff --git a/Adjust/ADJTimerOnce.m b/Adjust/ADJTimerOnce.m index 1cae53c6a..d5eec7323 100644 --- a/Adjust/ADJTimerOnce.m +++ b/Adjust/ADJTimerOnce.m @@ -7,6 +7,9 @@ // #import "ADJTimerOnce.h" +#import "ADJLogger.h" +#import "ADJAdjustFactory.h" +#import "ADJUtil.h" static const uint64_t kTimerLeeway = 1 * NSEC_PER_SEC; // 1 second @@ -18,6 +21,8 @@ @interface ADJTimerOnce() @property (nonatomic, strong) dispatch_block_t block; @property (nonatomic, assign, readonly) dispatch_time_t start; @property (nonatomic, retain) NSDate * fireDate; +@property (nonatomic, retain) id logger; +@property (nonatomic, copy) NSString *name; @end @@ -26,19 +31,26 @@ @implementation ADJTimerOnce + (ADJTimerOnce *)timerWithBlock:(dispatch_block_t)block queue:(dispatch_queue_t)queue + name:(NSString*)name { - return [[ADJTimerOnce alloc] initBlock:block queue:queue]; + return [[ADJTimerOnce alloc] initBlock:block queue:queue name:name]; } - (id)initBlock:(dispatch_block_t)block queue:(dispatch_queue_t)queue + name:(NSString*)name { self = [super init]; if (self == nil) return nil; self.internalQueue = queue; + self.logger = ADJAdjustFactory.logger; + self.name = name; - self.block = block; + self.block = ^{ + [ADJAdjustFactory.logger verbose:@"%@ fired", name]; + block(); + }; return self; } @@ -50,13 +62,13 @@ - (NSTimeInterval)fireIn { return [self.fireDate timeIntervalSinceNow]; } -- (void)startIn:(NSTimeInterval)startIn -{ - self.fireDate = [[NSDate alloc] initWithTimeIntervalSinceNow:startIn]; +- (void)startIn:(NSTimeInterval)startIn { + // cancel previous + [self cancel:NO]; - if (self.source != nil) { - dispatch_cancel(self.source); - } + self.fireDate = [[NSDate alloc] initWithTimeIntervalSinceNow:startIn]; + NSString * fireInFormatted = [ADJUtil secondsNumberFormat:[self fireIn]]; + [self.logger verbose:@"%@ starting. Launching in %@ seconds", self.name, fireInFormatted]; self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, self.internalQueue); @@ -71,4 +83,17 @@ - (void)startIn:(NSTimeInterval)startIn dispatch_source_set_event_handler(self.source, self.block); } +- (void)cancel:(BOOL)log { + if (self.source != nil) { + dispatch_cancel(self.source); + } + self.source = nil; + if (log) { + [self.logger verbose:@"%@ canceled", self.name]; + } +} + +- (void)cancel { + [self cancel:YES]; +} @end diff --git a/Adjust/ADJUtil.h b/Adjust/ADJUtil.h index 7f048ef22..b4556e937 100644 --- a/Adjust/ADJUtil.h +++ b/Adjust/ADJUtil.h @@ -10,6 +10,7 @@ #import "ADJResponseData.h" #import "ADJActivityPackage.h" #import "ADJEvent.h" +#import "ADJBackoffStrategy.h" @interface ADJUtil : NSObject @@ -36,6 +37,14 @@ + (NSString *) queryString:(NSDictionary *)parameters; + (BOOL)isNull:(id)value; + (BOOL)isNotNull:(id)value; + ++ (void)sendPostRequest:(NSURL *)baseUrl + queueSize:(NSUInteger)queueSize + prefixErrorMessage:(NSString *)prefixErrorMessage + suffixErrorMessage:(NSString *)suffixErrorMessage + activityPackage:(ADJActivityPackage *)activityPackage + responseDataHandler:(void (^) (ADJResponseData * responseData))responseDataHandler; + + (void)sendRequest:(NSMutableURLRequest *)request prefixErrorMessage:(NSString *)prefixErrorMessage activityPackage:(ADJActivityPackage *)activityPackage @@ -51,5 +60,11 @@ responseDataHandler:(void (^) (ADJResponseData * responseData))responseDataHandl + (NSURL*)convertUniversalLink:(NSURL *)url scheme:(NSString *)scheme; + (NSString*)idfa; - ++ (NSString *)secondsNumberFormat:(double)seconds; ++ (NSTimeInterval)waitingTime:(NSInteger)retries + backoffStrategy:(ADJBackoffStrategy *)backoffStrategy; ++ (void)launchInMainThread:(NSObject *)receiver + selector:(SEL)selector + withObject:(id)object; ++ (void)launchInMainThread:(dispatch_block_t)block; @end diff --git a/Adjust/ADJUtil.m b/Adjust/ADJUtil.m index 888309e7d..96226340b 100644 --- a/Adjust/ADJUtil.m +++ b/Adjust/ADJUtil.m @@ -15,15 +15,20 @@ #import "ADJResponseData.h" #include +#include +#include static NSDateFormatter *dateFormat; -static NSString * const kClientSdk = @"ios4.6.0"; +static NSString * const kClientSdk = @"ios4.7.0"; static NSString * const kDefaultScheme = @"AdjustUniversalScheme"; static NSString * const kUniversalLinkPattern = @"https://[^.]*\\.ulink\\.adjust\\.com/ulink/?(.*)"; + static NSString * const kBaseUrl = @"https://app.adjust.com"; static NSString * const kDateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'Z"; static NSRegularExpression * universalLinkRegex = nil; +static NSNumberFormatter * secondsNumberFormatter = nil; +static const double kRequestTimeout = 60; // 60 seconds #pragma mark - @implementation ADJUtil @@ -48,7 +53,6 @@ + (void) initialize { #pragma clang diagnostic pop } - dateFormat.calendar = [NSCalendar calendarWithIdentifier:calendarIdentifier]; } @@ -202,6 +206,12 @@ + (void)writeObject:(id)object } + (NSString *) queryString:(NSDictionary *)parameters { + return [ADJUtil queryString:parameters queueSize:0]; +} + ++ (NSString *) queryString:(NSDictionary *)parameters + queueSize:(NSUInteger)queueSize +{ NSMutableArray *pairs = [NSMutableArray array]; for (NSString *key in parameters) { NSString *value = [parameters objectForKey:key]; @@ -215,11 +225,17 @@ + (NSString *) queryString:(NSDictionary *)parameters { NSString *dateString = [ADJUtil formatSeconds1970:now]; NSString *escapedDate = [dateString adjUrlEncode]; NSString *sentAtPair = [NSString stringWithFormat:@"%@=%@", @"sent_at", escapedDate]; - [pairs addObject:sentAtPair]; + if (queueSize > 0) { + NSString *queueSizeString = [NSString stringWithFormat:@"%lu", queueSize]; + NSString *escapedQueueSize = [queueSizeString adjUrlEncode]; + NSString *queueSizePair = [NSString stringWithFormat:@"%@=%@", @"queue_size", escapedQueueSize]; + [pairs addObject:queueSizePair]; + } + NSString *queryString = [pairs componentsJoinedByString:@"&"]; - + return queryString; } @@ -243,6 +259,41 @@ + (NSString *)formatErrorMessage:(NSString *)prefixErrorMessage } } ++ (void)sendPostRequest:(NSURL *)baseUrl + queueSize:(NSUInteger)queueSize + prefixErrorMessage:(NSString *)prefixErrorMessage + suffixErrorMessage:(NSString *)suffixErrorMessage + activityPackage:(ADJActivityPackage *)activityPackage + responseDataHandler:(void (^) (ADJResponseData * responseData))responseDataHandler +{ + NSMutableURLRequest * request = [ADJUtil requestForPackage:activityPackage baseUrl:baseUrl queueSize:queueSize]; + + [ADJUtil sendRequest:request + prefixErrorMessage:prefixErrorMessage + suffixErrorMessage:suffixErrorMessage + activityPackage:activityPackage + responseDataHandler:responseDataHandler]; +} + ++ (NSMutableURLRequest *)requestForPackage:(ADJActivityPackage *)activityPackage + baseUrl:(NSURL *)baseUrl + queueSize:(NSUInteger)queueSize +{ + NSURL *url = [NSURL URLWithString:activityPackage.path relativeToURL:baseUrl]; + NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; + request.timeoutInterval = kRequestTimeout; + request.HTTPMethod = @"POST"; + + [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; + [request setValue:activityPackage.clientSdk forHTTPHeaderField:@"Client-Sdk"]; + + NSString *bodyString = [ADJUtil queryString:activityPackage.parameters queueSize:queueSize]; + NSData *body = [NSData dataWithBytes:bodyString.UTF8String length:bodyString.length]; + [request setHTTPBody:body]; + + return request; +} + + (void)sendRequest:(NSMutableURLRequest *)request prefixErrorMessage:(NSString *)prefixErrorMessage activityPackage:(ADJActivityPackage *)activityPackage @@ -485,4 +536,73 @@ + (NSURL *)convertUniversalLink:(NSURL *)url scheme:(NSString *)scheme { return extractedUrl; } ++ (NSString *)secondsNumberFormat:(double)seconds { + if (secondsNumberFormatter == nil) { + secondsNumberFormatter = [[NSNumberFormatter alloc] init]; + [secondsNumberFormatter setPositiveFormat:@"0.0"]; + } + + // normalize negative zero + if (seconds < 0) { + seconds = seconds * -1; + } + + return [secondsNumberFormatter stringFromNumber:[NSNumber numberWithDouble:seconds]]; +} + ++ (double)randomInRange:(double) minRange maxRange:(double) maxRange { + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + srand48(arc4random()); + }); + + double random = drand48(); + double range = maxRange - minRange; + double scaled = random * range; + double shifted = scaled + minRange; + return shifted; +} + ++ (NSTimeInterval)waitingTime:(NSInteger)retries + backoffStrategy:(ADJBackoffStrategy *)backoffStrategy +{ + if (retries < backoffStrategy.minRetries) { + return 0; + } + // start with base 0 + NSInteger base = retries - backoffStrategy.minRetries; + // get the exponential Time from the base: 1, 2, 4, 8, 16, ... * times the multiplier + NSTimeInterval exponentialTime = pow(2.0, base) * backoffStrategy.secondMultiplier; + // limit the maximum allowed time to wait + NSTimeInterval ceilingTime = MIN(exponentialTime, backoffStrategy.maxWait); + // add 1 to allow maximum value + double randomRange = [ADJUtil randomInRange:backoffStrategy.minRange maxRange:backoffStrategy.maxRange]; + // apply jitter factor + NSTimeInterval waitingTime = ceilingTime * randomRange; + return waitingTime; +} + ++ (void)launchInMainThread:(NSObject *)receiver + selector:(SEL)selector + withObject:(id)object +{ + if (ADJAdjustFactory.testing) { + [ADJAdjustFactory.logger debug:@"Launching in the background for testing"]; + [receiver performSelectorInBackground:selector + withObject:object]; + } else { + [receiver performSelectorOnMainThread:selector + withObject:object + waitUntilDone:NO]; // non-blocking + } +} + ++ (void)launchInMainThread:(dispatch_block_t)block { + if (ADJAdjustFactory.testing) { + [ADJAdjustFactory.logger debug:@"Launching in the background for testing"]; + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), block); + } else { + dispatch_async(dispatch_get_main_queue(), block); + } +} @end diff --git a/Adjust/Adjust.h b/Adjust/Adjust.h index bd99788f9..8ae9251b1 100644 --- a/Adjust/Adjust.h +++ b/Adjust/Adjust.h @@ -120,4 +120,3 @@ extern NSString * const ADJEnvironmentProduction; - (NSString*)idfa; @end - diff --git a/Adjust/Adjust.m b/Adjust/Adjust.m index d45e3594c..774e734fc 100644 --- a/Adjust/Adjust.m +++ b/Adjust/Adjust.m @@ -109,12 +109,12 @@ - (void)trackEvent:(ADJEvent *)event { - (void)trackSubsessionStart { if (![self checkActivityHandler]) return; - [self.activityHandler trackSubsessionStart]; + [self.activityHandler applicationDidBecomeActive]; } - (void)trackSubsessionEnd { if (![self checkActivityHandler]) return; - [self.activityHandler trackSubsessionEnd]; + [self.activityHandler applicationWillResignActive]; } - (void)setEnabled:(BOOL)enabled { diff --git a/AdjustTests/ADJActivityHandlerMock.m b/AdjustTests/ADJActivityHandlerMock.m index f350cfb68..71a3b0e8a 100644 --- a/AdjustTests/ADJActivityHandlerMock.m +++ b/AdjustTests/ADJActivityHandlerMock.m @@ -32,11 +32,11 @@ - (id)initWithConfig:(ADJConfig *)adjustConfig { return self; } -- (void)trackSubsessionStart { - [self.loggerMock test:[prefix stringByAppendingFormat:@"trackSubsessionStart"]]; +- (void)applicationDidBecomeActive { + [self.loggerMock test:[prefix stringByAppendingFormat:@"applicationDidBecomeActive"]]; } -- (void)trackSubsessionEnd { - [self.loggerMock test:[prefix stringByAppendingFormat:@"trackSubsessionEnd"]]; +- (void)applicationWillResignActive { + [self.loggerMock test:[prefix stringByAppendingFormat:@"applicationWillResignActive"]]; } - (void)trackEvent:(ADJEvent *)event { @@ -49,6 +49,21 @@ - (void)finishedTracking:(ADJResponseData *)responseData { self.lastResponseData = responseData; } +- (void)launchEventResponseTasks:(ADJEventResponseData *)eventResponseData { + [self.loggerMock test:[prefix stringByAppendingFormat:@"launchEventResponseTasks, %@", eventResponseData]]; + self.lastResponseData = eventResponseData; +} + +- (void)launchSessionResponseTasks:(ADJSessionResponseData *)sessionResponseData { + [self.loggerMock test:[prefix stringByAppendingFormat:@"launchSessionResponseTasks, %@", sessionResponseData]]; + self.lastResponseData = sessionResponseData; +} + +- (void)launchAttributionResponseTasks:(ADJAttributionResponseData *)attributionResponseData { + [self.loggerMock test:[prefix stringByAppendingFormat:@"launchAttributionResponseTasks, %@", attributionResponseData]]; + self.lastResponseData = attributionResponseData; +} + - (void)setEnabled:(BOOL)enabled { [self.loggerMock test:[prefix stringByAppendingFormat:@"setEnabled enabled:%d", enabled]]; } @@ -95,23 +110,12 @@ - (void)setIadDetails:(NSDictionary *)attributionDetails [self.loggerMock test:[prefix stringByAppendingFormat:@"setIadDetails, %@ error, %@", attributionDetails, error]]; } -- (void)launchEventResponseTasks:(ADJEventResponseData *)eventResponseData { - [self.loggerMock test:[prefix stringByAppendingFormat:@"launchEventResponseTasks, %@", eventResponseData]]; - self.lastResponseData = eventResponseData; -} - -- (void)launchSessionResponseTasks:(ADJSessionResponseData *)sessionResponseData { - [self.loggerMock test:[prefix stringByAppendingFormat:@"launchSessionResponseTasks, %@", sessionResponseData]]; - self.lastResponseData = sessionResponseData; -} - -- (void)launchAttributionResponseTasks:(ADJAttributionResponseData *)attributionResponseData { - [self.loggerMock test:[prefix stringByAppendingFormat:@"launchAttributionResponseTasks, %@", attributionResponseData]]; - self.lastResponseData = attributionResponseData; -} - - (void) setOfflineMode:(BOOL)enabled { [self.loggerMock test:[prefix stringByAppendingFormat:@"setOfflineMode"]]; } +- (ADJInternalState*) internalState { + return nil; +} + @end diff --git a/AdjustTests/ADJActivityHandlerTests.m b/AdjustTests/ADJActivityHandlerTests.m index b7ab05557..b420ab0d0 100644 --- a/AdjustTests/ADJActivityHandlerTests.m +++ b/AdjustTests/ADJActivityHandlerTests.m @@ -17,15 +17,18 @@ #import "ADJLogger.h" #import "ADJAttributionHandlerMock.h" #import "ADJConfig.h" -#import "ADJDelegateTest.h" +#import "ADJAttributionChangedDelegate.h" #import "ADJTestActivityPackage.h" -#import "ADJTrackingSucceededDelegate.h" -#import "ADJTrackingFailedDelegate.h" +#import "ADJTrackingDelegate.h" +#import "ADJSdkClickHandlerMock.h" +#import "ADJSessionState.h" +#import "ADJDeeplinkDelegate.h" @interface ADJActivityHandlerTests : ADJTestActivityPackage @property (atomic,strong) ADJPackageHandlerMock *packageHandlerMock; @property (atomic,strong) ADJAttributionHandlerMock *attributionHandlerMock; +@property (atomic,strong) ADJSdkClickHandlerMock * sdkClickHandlerMock; @end @@ -42,7 +45,9 @@ - (void)setUp - (void)tearDown { + [ADJAdjustFactory setTesting:NO]; [ADJAdjustFactory setPackageHandler:nil]; + [ADJAdjustFactory setSdkClickHandler:nil]; [ADJAdjustFactory setLogger:nil]; [ADJAdjustFactory setSessionInterval:-1]; [ADJAdjustFactory setSubsessionInterval:-1]; @@ -54,11 +59,15 @@ - (void)tearDown } - (void)reset { + [ADJAdjustFactory setTesting:YES]; + self.loggerMock = [[ADJLoggerMock alloc] init]; [ADJAdjustFactory setLogger:self.loggerMock]; self.packageHandlerMock = [ADJPackageHandlerMock alloc]; [ADJAdjustFactory setPackageHandler:self.packageHandlerMock]; + self.sdkClickHandlerMock = [ADJSdkClickHandlerMock alloc]; + [ADJAdjustFactory setSdkClickHandler:self.sdkClickHandlerMock]; [ADJAdjustFactory setSessionInterval:-1]; [ADJAdjustFactory setSubsessionInterval:-1]; @@ -79,26 +88,10 @@ - (void)testFirstSession [self reset]; // create the config to start the session - ADJConfig * config = [ADJConfig configWithAppToken:@"123456789012" environment:ADJEnvironmentSandbox]; + ADJConfig * config = [self getConfig]; // create handler and start the first session - [ADJActivityHandler handlerWithConfig:config]; - - // it's necessary to sleep the activity for a while after each handler call - // to let the internal queue act - [NSThread sleepForTimeInterval:2.0]; - - // test init values - [self checkInit:ADJEnvironmentSandbox logLevel:@"3"]; - - // check event buffering is disabled - anInfo(@"Event buffering is enabled"); - - // check does not have default tracker - anInfo(@"Default tracker:"); - - // test first session start - [self checkFirstSession]; + [self startAndCheckFirstSession:config]; // checking the default values of the first session package // should only have one package @@ -119,33 +112,31 @@ - (void)testEventBuffered [self reset]; // create the config to start the session - ADJConfig * config = [ADJConfig configWithAppToken:@"123456789012" environment:ADJEnvironmentSandbox]; + ADJConfig * config = [self getConfig:ADJLogLevelVerbose]; // buffer events config.eventBufferingEnabled = YES; - // set verbose log level - config.logLevel = ADJLogLevelVerbose; - // set default tracker [config setDefaultTracker:@"default1234tracker"]; // create handler and start the first session - id activityHandler = [ADJActivityHandler handlerWithConfig:config]; + id activityHandler = [self getFirstActivityHandler:config logLevel:ADJLogLevelVerbose]; [NSThread sleepForTimeInterval:2.0]; // test init values - [self checkInit:ADJEnvironmentSandbox logLevel:@"1"]; + [self checkInitTests:YES defaultTracker:@"default1234tracker" startsSending:NO]; - // check event buffering is enabled - aInfo(@"Event buffering is enabled"); + [self startActivity:activityHandler]; - // check does have default tracker - aInfo(@"Default tracker: default1234tracker"); + [NSThread sleepForTimeInterval:2.0]; + + ADJSessionState * sessionState = [ADJSessionState sessionStateWithSessionType:ADJSessionTypeSession]; + sessionState.eventBufferingIsEnabled = YES; // test first session start - [self checkFirstSession]; + [self checkStartInternal:sessionState]; // create the first Event object with callback and partner parameters ADJEvent * firstEvent = [ADJEvent eventWithEventToken:@"event1"]; @@ -186,6 +177,9 @@ - (void)testEventBuffered // and not sent to package handler anTest(@"PackageHandler sendFirstPackage"); + // does not fire background timer + anVerbose(@"Background timer starting"); + // after tracking the event it should write the activity state aDebug(@"Wrote Activity state: ec:1"); @@ -228,16 +222,15 @@ - (void)testEventBuffered // and not sent to package handler anTest(@"PackageHandler sendFirstPackage"); + // does not fire background timer + anVerbose(@"Background timer starting"); + // after tracking the event it should write the activity state aDebug(@"Wrote Activity state: ec:2"); // create a forth Event object without revenue ADJEvent * forthEvent = [ADJEvent eventWithEventToken:@"event4"]; - // test push token - const char bytes[] = "\xFC\x07\x21\xB6\xDF\xAD\x5E\xE1\x10\x97\x5B\xB2\xA2\x63\xDE\x00\x61\xCC\x70\x5B\x4A\x85\xA8\xAE\x3C\xCF\xBE\x7A\x66\x2F\xB1\xAB"; - [activityHandler setDeviceToken:[NSData dataWithBytes:bytes length:(sizeof(bytes) - 1)]]; - // track the forth event [activityHandler trackEvent:forthEvent]; @@ -252,6 +245,9 @@ - (void)testEventBuffered // and not sent to package handler anTest(@"PackageHandler sendFirstPackage"); + // does not fire background timer + anVerbose(@"Background timer starting"); + // after tracking the event it should write the activity state aDebug(@"Wrote Activity state: ec:3"); @@ -266,6 +262,7 @@ - (void)testEventBuffered ADJPackageFields * firstSessionPackageFields = [ADJPackageFields fields]; firstSessionPackageFields.defaultTracker = @"default1234tracker"; + firstSessionPackageFields.eventBufferingEnabled = 1; // test first session [self testPackageSession:sessionPackage fields:firstSessionPackageFields sessionCount:@"1"]; @@ -283,9 +280,10 @@ - (void)testEventBuffered firstPackageFields.callbackParameters = @"{\"keyCall\":\"valueCall2\",\"fooCall\":\"barCall\"}"; firstPackageFields.partnerParameters = @"{\"keyPartner\":\"valuePartner2\",\"fooPartner\":\"barPartner\"}"; firstPackageFields.suffix = @"(0.00100 EUR, 'event1')"; + firstPackageFields.eventBufferingEnabled = 1; // test first event - [self testEventSession:firstEventPackage fields:firstPackageFields eventToken:@"event1"]; + [self testEventPackage:firstEventPackage fields:firstPackageFields eventToken:@"event1"]; // third event ADJActivityPackage * thirdEventPackage = (ADJActivityPackage *) self.packageHandlerMock.packageQueue[2]; @@ -299,9 +297,10 @@ - (void)testEventBuffered thirdPackageFields.currency = @"USD"; thirdPackageFields.suffix = @"(0.00000 USD, 'event3')"; thirdPackageFields.receipt = @"eyAidHJhbnNhY3Rpb24taWQiID0gInRfaWRfMiI7IH0"; + thirdPackageFields.eventBufferingEnabled = 1; // test third event - [self testEventSession:thirdEventPackage fields:thirdPackageFields eventToken:@"event3"]; + [self testEventPackage:thirdEventPackage fields:thirdPackageFields eventToken:@"event3"]; // fourth event ADJActivityPackage * fourthEventPackage = (ADJActivityPackage *) self.packageHandlerMock.packageQueue[3]; @@ -312,10 +311,57 @@ - (void)testEventBuffered // set event test parameters fourthPackageFields.eventCount = @"3"; fourthPackageFields.suffix = @"'event4'"; - fourthPackageFields.pushToken = @"fc0721b6dfad5ee110975bb2a263de0061cc705b4a85a8ae3ccfbe7a662fb1ab"; + fourthPackageFields.eventBufferingEnabled = 1; // test fourth event - [self testEventSession:fourthEventPackage fields:fourthPackageFields eventToken:@"event4"]; + [self testEventPackage:fourthEventPackage fields:fourthPackageFields eventToken:@"event4"]; +} + +- (void)testEventsNotBuffered +{ + // reseting to make the test order independent + [self reset]; + + // create the config to start the session + ADJConfig * config = [self getConfig:ADJLogLevelDebug]; + + // start activity handler with config + id activityHandler = [self getFirstActivityHandler:config logLevel:ADJLogLevelDebug]; + + [NSThread sleepForTimeInterval:2.0]; + + // test init values + [self checkInitTests]; + + [self startActivity:activityHandler]; + + [NSThread sleepForTimeInterval:2.0]; + + // test session + [self checkFirstSession]; + + // create the first Event + ADJEvent * firstEvent = [ADJEvent eventWithEventToken:@"event1"]; + + // track event + [activityHandler trackEvent:firstEvent]; + + [NSThread sleepForTimeInterval:2]; + + // check that event package was added + aTest(@"PackageHandler addPackage"); + + // check that event was sent to package handler + aTest(@"PackageHandler sendFirstPackage"); + + // and not buffered + anInfo(@"Buffered event"); + + // does not fire background timer + anVerbose(@"Background timer starting"); + + // after tracking the event it should write the activity state + aDebug(@"Wrote Activity state"); } - (void)testChecks @@ -372,10 +418,7 @@ - (void)testChecks aFalse(malformedTokenEvent.isValid); // create the config to start the session - ADJConfig * config = [ADJConfig configWithAppToken:@"123456789012" environment:ADJEnvironmentSandbox]; - - // set the log level - config.logLevel = ADJLogLevelDebug; + ADJConfig * config = [self getConfig:ADJLogLevelInfo]; // set the delegate that doesn't implement the optional selector ADJTestsUtil * delegateNotImpl = [[ADJTestsUtil alloc] init]; @@ -384,12 +427,16 @@ - (void)testChecks aError(@"Delegate does not implement any optional method"); // create handler and start the first session - id activityHandler =[ADJActivityHandler handlerWithConfig:config]; + id activityHandler = [self getFirstActivityHandler:config logLevel:ADJLogLevelInfo]; [NSThread sleepForTimeInterval:2]; // test init values - [self checkInit:ADJEnvironmentSandbox logLevel:@"2"]; + [self checkInitTests]; + + [self startActivity:activityHandler]; + + [NSThread sleepForTimeInterval:2.0]; // test first session start [self checkFirstSession]; @@ -496,8 +543,6 @@ - (void)testChecks // create activity package test ADJPackageFields * sessionPackageFields = [ADJPackageFields fields]; - sessionPackageFields.hasDelegate = @"0"; - // test first session [self testPackageSession:sessionPackage fields:sessionPackageFields sessionCount:@"1"]; @@ -512,10 +557,10 @@ - (void)testChecks eventFields.suffix = @"'event1'"; // test first event - [self testEventSession:eventPackage fields:eventFields eventToken:@"event1"]; + [self testEventPackage:eventPackage fields:eventFields eventToken:@"event1"]; } -- (void)testSessons +- (void)testSessions { // reseting to make the test order independent [self reset]; @@ -525,75 +570,81 @@ - (void)testSessons [ADJAdjustFactory setSubsessionInterval:(1)]; // 1 second // create the config to start the session - ADJConfig * config = [ADJConfig configWithAppToken:@"123456789012" environment:ADJEnvironmentSandbox]; - - // set verbose log level - config.logLevel = ADJLogLevelInfo; + ADJConfig * config = [self getConfig:ADJLogLevelWarn]; // create handler and start the first session - id activityHandler = [ADJActivityHandler handlerWithConfig:config]; + id activityHandler = [self startAndCheckFirstSession:config logLevel:ADJLogLevelWarn]; - [NSThread sleepForTimeInterval:2]; + [self stopActivity:activityHandler]; - // test init values - [self checkInit:ADJEnvironmentSandbox logLevel:@"3"]; + [NSThread sleepForTimeInterval:1]; - // test first session start - [self checkFirstSession]; + // test the end of the subsession + [self checkEndSession]; - // trigger a new sub session session - [activityHandler trackSubsessionStart]; + [self startActivity:activityHandler]; - // and end it - [activityHandler trackSubsessionEnd]; + [NSThread sleepForTimeInterval:1]; - [NSThread sleepForTimeInterval:5]; + // test the new sub session + ADJSessionState * secondSubsession = [ADJSessionState sessionStateWithSessionType:ADJSessionTypeSubSession]; + secondSubsession.subsessionCount = 2; + [self checkStartInternal:secondSubsession]; - [self checkSubsession:1 subSessionCount:2 timerAlreadyStarted:YES]; + [self stopActivity:activityHandler]; + [NSThread sleepForTimeInterval:5]; + + // test the end of the subsession [self checkEndSession]; // trigger a new session - [activityHandler trackSubsessionStart]; + [self startActivity:activityHandler]; [NSThread sleepForTimeInterval:1]; // new session - [self checkNewSession:NO - sessionCount:2 - eventCount:0 - timerAlreadyStarted:YES]; + ADJSessionState * secondSession = [ADJSessionState sessionStateWithSessionType:ADJSessionTypeSession]; + secondSession.sessionCount = 2; + secondSession.timerAlreadyStarted = YES; + [self checkStartInternal:secondSession]; - // end the session - [activityHandler trackSubsessionEnd]; + // stop and start the activity with little interval + // so it won't trigger a sub session + [self stopActivity:activityHandler]; + [self startActivity:activityHandler]; [NSThread sleepForTimeInterval:1]; - [self checkEndSession]; + // test the end of the subsession + [self checkEndSession:NO updateActivityState:YES eventBufferingEnabled:NO]; + + // test non sub session + ADJSessionState * nonSessionState = [ADJSessionState sessionStateWithSessionType:ADJSessionTypeNonSession]; + [self checkStartInternal:nonSessionState]; // 2 session packages aiEquals(2, (int)[self.packageHandlerMock.packageQueue count]); - // first session - ADJActivityPackage *firstSessionPackage = (ADJActivityPackage *) self.packageHandlerMock.packageQueue[0]; + ADJActivityPackage * firstSessionActivityPackage = (ADJActivityPackage *) self.packageHandlerMock.packageQueue[0]; // create activity package test - ADJPackageFields * firstSessionfields = [ADJPackageFields fields]; + ADJPackageFields * firstSessionPackageFields = [ADJPackageFields fields]; // test first session - [self testPackageSession:firstSessionPackage fields:firstSessionfields sessionCount:@"1"]; + [self testPackageSession:firstSessionActivityPackage fields:firstSessionPackageFields sessionCount:@"1"]; // get second session package - ADJActivityPackage *secondSessionPackage = (ADJActivityPackage *) self.packageHandlerMock.packageQueue[1]; + ADJActivityPackage * secondSessionActivityPackage = (ADJActivityPackage *) self.packageHandlerMock.packageQueue[1]; // create second session test package - ADJPackageFields * secondSessionfields = [ADJPackageFields fields]; + ADJPackageFields * secondSessionPackageFields = [ADJPackageFields fields]; // check if it saved the second subsession in the new package - secondSessionfields.subSessionCount = @"2"; - + secondSessionPackageFields.subSessionCount = @"2"; + // test second session - [self testPackageSession:secondSessionPackage fields:secondSessionfields sessionCount:@"2"]; + [self testPackageSession:secondSessionActivityPackage fields:secondSessionPackageFields sessionCount:@"2"]; } - (void)testDisable @@ -610,16 +661,10 @@ - (void)testDisable [ADJAdjustFactory setSubsessionInterval:(1)]; // 1 second // create the config to start the session - ADJConfig * config = [ADJConfig configWithAppToken:@"123456789012" environment:ADJEnvironmentSandbox]; - - // set log level - config.logLevel = ADJLogLevelWarn; + ADJConfig * config = [self getConfig:ADJLogLevelError]; // start activity handler with config - id activityHandler = [ADJActivityHandler handlerWithConfig:config]; - - // check that is true by default - aTrue([activityHandler isEnabled]); + id activityHandler = [self getFirstActivityHandler:config logLevel:ADJLogLevelError]; // disable sdk [activityHandler setEnabled:NO]; @@ -627,42 +672,49 @@ - (void)testDisable // check that it is disabled aFalse([activityHandler isEnabled]); - // not writing activity state because it did not had time to start + [NSThread sleepForTimeInterval:2.0]; + + // not writing activity state because it set enable does not start the sdk anDebug(@"Wrote Activity state"); // check if message the disable of the SDK - aInfo(@"Pausing package handler and attribution handler to disable the SDK"); - - // it's necessary to sleep the activity for a while after each handler call - // to let the internal queue act - [NSThread sleepForTimeInterval:2]; + aInfo(@"Package handler and attribution handler will start as paused due to the SDK being disabled"); // test init values - [self checkInit:ADJEnvironmentSandbox logLevel:@"4"]; + [self checkInitTests:NO]; + + [self checkHandlerStatus:YES]; + + // start the sdk + // foreground timer does not start because it's paused + [self startActivity:activityHandler]; + + [NSThread sleepForTimeInterval:2.0]; + + ADJSessionState * sessionStartsPaused = [ADJSessionState sessionStateWithSessionType:ADJSessionTypeSession]; + + sessionStartsPaused.paused = YES; + sessionStartsPaused.toSend = NO; + sessionStartsPaused.foregroundTimerStarts = NO; + sessionStartsPaused.foregroundTimerAlreadyStarted = NO; + + // check session that is paused + [self checkStartInternal:sessionStartsPaused]; - // test first session start without attribution handler - [self checkFirstSession:YES]; + [self stopActivity:activityHandler]; + + [NSThread sleepForTimeInterval:1.0]; // test end session of disable - [self checkEndSession]; + [self checkEndSession:YES updateActivityState:YES eventBufferingEnabled:NO]; // try to do activities while SDK disabled - [activityHandler trackSubsessionStart]; + [activityHandler applicationDidBecomeActive]; [activityHandler trackEvent:[ADJEvent eventWithEventToken:@"event1"]]; [NSThread sleepForTimeInterval:3]; - // check that timer was not executed - anDebug(@"Session timer fired"); - - // check that it did not resume - anTest(@"PackageHandler resumeSending"); - - // check that it did not wrote activity state from new session or subsession - anDebug(@"Wrote Activity state"); - - // check that it did not add any event package - anTest(@"PackageHandler addPackage"); + [self checkStartDisable]; // only the first session package should be sent aiEquals(1, (int)[self.packageHandlerMock.packageQueue count]); @@ -671,13 +723,14 @@ - (void)testDisable [activityHandler setOfflineMode:YES]; // pausing due to offline mode - aInfo(@"Pausing package and attribution handler to put in offline mode"); + aInfo(@"Pausing package and attribution handler to put SDK offline mode"); // wait to update status - [NSThread sleepForTimeInterval:6.0]; + [NSThread sleepForTimeInterval:5.0]; - // test end session of offline - [self checkEndSession]; + // after pausing, even when it's already paused + // tries to update the status + [self checkHandlerStatus:YES]; // re-enable the SDK [activityHandler setEnabled:YES]; @@ -686,20 +739,33 @@ - (void)testDisable aTrue([activityHandler isEnabled]); // check message of SDK still paused - aInfo(@"Package and attribution handler remain paused due to the SDK is offline"); + aInfo(@"Package and attribution handler remain paused due to SDK being offline"); - [activityHandler trackSubsessionStart]; [NSThread sleepForTimeInterval:1.0]; - [self checkNewSession:YES sessionCount:2 eventCount:0 timerAlreadyStarted:NO]; + // due to the fact it will remained paused, + // there is no need to try to update the status + [self checkHandlerStatusNotCalled]; + + // start the sdk + // foreground timer does not start because it's offline + [self startActivity:activityHandler]; + + [NSThread sleepForTimeInterval:1.0]; + + ADJSessionState * secondPausedSession = [ADJSessionState sessionStateWithSessionType:ADJSessionTypeSession]; + + secondPausedSession.toSend = NO; + secondPausedSession.paused = YES; + secondPausedSession.sessionCount = 2; + secondPausedSession.foregroundTimerStarts = NO; + sessionStartsPaused.foregroundTimerAlreadyStarted = NO; + [self checkStartInternal:secondPausedSession]; - // and that the timer is not fired - anDebug(@"Session timer fired"); - // track an event [activityHandler trackEvent:[ADJEvent eventWithEventToken:@"event1"]]; - [NSThread sleepForTimeInterval:1.0]; + [NSThread sleepForTimeInterval:5.0]; // check that it did add the event package aTest(@"PackageHandler addPackage"); @@ -707,6 +773,9 @@ - (void)testDisable // and send it aTest(@"PackageHandler sendFirstPackage"); + // does not fire background timer + anVerbose(@"Background timer starting"); + // it should have the second session and the event aiEquals(3, (int)[self.packageHandlerMock.packageQueue count]); @@ -728,27 +797,38 @@ - (void)testDisable eventFields.suffix = @"'event1'"; // test event - [self testEventSession:eventPackage fields:eventFields eventToken:@"event1"]; + [self testEventPackage:eventPackage fields:eventFields eventToken:@"event1"]; + + // end the session + [self stopActivity:activityHandler]; + + [NSThread sleepForTimeInterval:1.0]; + + [self checkEndSession]; // put in online mode [activityHandler setOfflineMode:NO]; // message that is finally resuming - aInfo(@"Resuming package handler and attribution handler to put in online mode"); + aInfo(@"Resuming package handler and attribution handler to put SDK in online mode"); - [NSThread sleepForTimeInterval:6.0]; + [NSThread sleepForTimeInterval:1.0]; - // check status update - aTest(@"AttributionHandler resumeSending"); - aTest(@"PackageHandler resumeSending"); + // after un-pausing the sdk, tries to update the handlers + // it is still paused because it's on the background + [self checkHandlerStatus:YES]; - // track sub session - [activityHandler trackSubsessionStart]; + [self startActivity:activityHandler]; [NSThread sleepForTimeInterval:1.0]; // test sub session not paused - [self checkNewSession:NO sessionCount:3 eventCount:1 timerAlreadyStarted:YES]; + ADJSessionState * thirdSessionStarting = [ADJSessionState sessionStateWithSessionType:ADJSessionTypeSession]; + thirdSessionStarting.sessionCount = 3; + thirdSessionStarting.eventCount = 1; + thirdSessionStarting.timerAlreadyStarted = NO; + + [self checkStartInternal:thirdSessionStarting]; } - (void)testAppWillOpenUrl @@ -757,23 +837,11 @@ - (void)testAppWillOpenUrl [self reset]; // create the config to start the session - ADJConfig * config = [ADJConfig configWithAppToken:@"123456789012" environment:ADJEnvironmentSandbox]; - - // set log level - config.logLevel = ADJLogLevelError; + ADJConfig * config = [self getConfig:ADJLogLevelAssert]; // start activity handler with config - id activityHandler = [ADJActivityHandler handlerWithConfig:config]; - - // it's necessary to sleep the activity for a while after each handler call - // to let the internal queue act - [NSThread sleepForTimeInterval:2.0]; - - // test init values - [self checkInit:ADJEnvironmentSandbox logLevel:@"5"]; - - // test first session start - [self checkFirstSession]; + id activityHandler = [self startAndCheckFirstSession:config + logLevel:ADJLogLevelAssert]; NSURL* attributions = [NSURL URLWithString:@"AdjustTests://example.com/path/inApp?adjust_tracker=trackerValue&other=stuff&adjust_campaign=campaignValue&adjust_adgroup=adgroupValue&adjust_creative=creativeValue"]; NSURL* extraParams = [NSURL URLWithString:@"AdjustTests://example.com/path/inApp?adjust_foo=bar&other=stuff&adjust_key=value"]; @@ -800,20 +868,20 @@ - (void)testAppWillOpenUrl [activityHandler appWillOpenUrl:prefix]; [activityHandler appWillOpenUrl:incomplete]; - [NSThread sleepForTimeInterval:2]; + [NSThread sleepForTimeInterval:1]; // three click packages: attributions, extraParams and mixed - for (int i = 3; i > 0; i--) { - aTest(@"PackageHandler addPackage"); - aTest(@"PackageHandler sendFirstPackage"); + for (int i = 7; i > 0; i--) { + aTest(@"SdkClickHandler sendSdkClick"); } - // checking the default values of the first session package - // 1 session + 3 click - aiEquals(4, (int)[self.packageHandlerMock.packageQueue count]); + anTest(@"SdkClickHandler sendSdkClick"); + + // 7 clicks + aiEquals(7, (int)[self.sdkClickHandlerMock.packageQueue count]); // get the click package - ADJActivityPackage * attributionClickPackage = (ADJActivityPackage *) self.packageHandlerMock.packageQueue[1]; + ADJActivityPackage * attributionClickPackage = (ADJActivityPackage *) self.sdkClickHandlerMock.packageQueue[0]; // create activity package test ADJPackageFields * attributionClickFields = [ADJPackageFields fields]; @@ -828,11 +896,13 @@ - (void)testAppWillOpenUrl // and set it attributionClickFields.attribution = firstAttribution; + attributionClickFields.deepLink = @"AdjustTests://example.com/path/inApp?adjust_tracker=trackerValue&other=stuff&adjust_campaign=campaignValue&adjust_adgroup=adgroupValue&adjust_creative=creativeValue"; + // test the first deeplink [self testClickPackage:attributionClickPackage fields:attributionClickFields source:@"deeplink"]; // get the click package - ADJActivityPackage * extraParamsClickPackage = (ADJActivityPackage *) self.packageHandlerMock.packageQueue[2]; + ADJActivityPackage * extraParamsClickPackage = (ADJActivityPackage *) self.sdkClickHandlerMock.packageQueue[1]; // create activity package test ADJPackageFields * extraParamsClickFields = [ADJPackageFields fields]; @@ -840,11 +910,13 @@ - (void)testAppWillOpenUrl // other deep link parameters extraParamsClickFields.deepLinkParameters = @"{\"key\":\"value\",\"foo\":\"bar\"}"; + extraParamsClickFields.deepLink = @"AdjustTests://example.com/path/inApp?adjust_foo=bar&other=stuff&adjust_key=value"; + // test the second deeplink [self testClickPackage:extraParamsClickPackage fields:extraParamsClickFields source:@"deeplink"]; // get the click package - ADJActivityPackage * mixedClickPackage = (ADJActivityPackage *) self.packageHandlerMock.packageQueue[3]; + ADJActivityPackage * mixedClickPackage = (ADJActivityPackage *) self.sdkClickHandlerMock.packageQueue[2]; // create activity package test ADJPackageFields * mixedClickFields = [ADJPackageFields fields]; @@ -858,11 +930,57 @@ - (void)testAppWillOpenUrl // and set it mixedClickFields.attribution = secondAttribution; + mixedClickFields.deepLink = @"AdjustTests://example.com/path/inApp?adjust_foo=bar&other=stuff&adjust_campaign=campaignValue&adjust_adgroup=adgroupValue&adjust_creative=creativeValue"; + // other deep link parameters mixedClickFields.deepLinkParameters = @"{\"foo\":\"bar\"}"; // test the third deeplink [self testClickPackage:mixedClickPackage fields:mixedClickFields source:@"deeplink"]; + + // get the click package + ADJActivityPackage * emptyQueryStringClickPackage = (ADJActivityPackage *) self.sdkClickHandlerMock.packageQueue[3]; + + // create activity package test + ADJPackageFields * emptyQueryStringClickFields = [ADJPackageFields fields]; + + emptyQueryStringClickFields.deepLink = @"AdjustTests://"; + + // test the second deeplink + [self testClickPackage:emptyQueryStringClickPackage fields:emptyQueryStringClickFields source:@"deeplink"]; + + // get the click package + ADJActivityPackage * singleClickPackage = (ADJActivityPackage *) self.sdkClickHandlerMock.packageQueue[4]; + + // create activity package test + ADJPackageFields * singleClickFields = [ADJPackageFields fields]; + + singleClickFields.deepLink = @"AdjustTests://example.com/path/inApp?adjust_foo"; + + // test the second deeplink + [self testClickPackage:singleClickPackage fields:singleClickFields source:@"deeplink"]; + + // get the click package + ADJActivityPackage * prefixClickPackage = (ADJActivityPackage *) self.sdkClickHandlerMock.packageQueue[5]; + + // create activity package test + ADJPackageFields * prefixClickFields = [ADJPackageFields fields]; + + prefixClickFields.deepLink = @"AdjustTests://example.com/path/inApp?adjust_=bar"; + + // test the second deeplink + [self testClickPackage:prefixClickPackage fields:prefixClickFields source:@"deeplink"]; + + // get the click package + ADJActivityPackage * incompleteClickPackage = (ADJActivityPackage *) self.sdkClickHandlerMock.packageQueue[6]; + + // create activity package test + ADJPackageFields * incompleteClickFields = [ADJPackageFields fields]; + + incompleteClickFields.deepLink = @"AdjustTests://example.com/path/inApp?adjust_foo="; + + // test the second deeplink + [self testClickPackage:incompleteClickPackage fields:incompleteClickFields source:@"deeplink"]; } - (void)testIadDates @@ -871,23 +989,10 @@ - (void)testIadDates [self reset]; // create the config to start the session - ADJConfig * config = [ADJConfig configWithAppToken:@"123456789012" environment:ADJEnvironmentSandbox]; - - // set log level - config.logLevel = ADJLogLevelAssert; - - // start activity handler with config - id activityHandler = [ADJActivityHandler handlerWithConfig:config]; - - // it's necessary to sleep the activity for a while after each handler call - // to let the internal queue act - [NSThread sleepForTimeInterval:2.0]; - - // test init values - [self checkInit:ADJEnvironmentSandbox logLevel:@"6"]; + ADJConfig * config = [self getConfig]; - // test first session start - [self checkFirstSession]; + // create handler and start the first session + id activityHandler =[self startAndCheckFirstSession:config]; // should be ignored [activityHandler setIadDate:nil withPurchaseDate:nil]; @@ -897,7 +1002,7 @@ - (void)testIadDates aDebug(@"iAdImpressionDate not received"); // didn't send click package - anTest(@"PackageHandler addPackage"); + anTest(@"SdkClickHandler sendSdkClick"); [activityHandler setIadDate:nil withPurchaseDate:[NSDate date]]; [NSThread sleepForTimeInterval:1]; @@ -906,7 +1011,7 @@ - (void)testIadDates aDebug(@"iAdImpressionDate not received"); // didn't send click package - anTest(@"PackageHandler addPackage"); + anTest(@"SdkClickHandler sendSdkClick"); // 1 session aiEquals(1, (int)[self.packageHandlerMock.packageQueue count]); @@ -925,29 +1030,29 @@ - (void)testIadDates [NSThread sleepForTimeInterval:1]; // iAdImpressionDate received - NSString * iAdImpressionDate1Log =[NSString stringWithFormat:@"iAdImpressionDate received: %@", date1]; + NSString * iAdImpressionDate1Log = [NSString stringWithFormat:@"iAdImpressionDate received: %@", date1]; aDebug(iAdImpressionDate1Log); // first iad package added - aTest(@"PackageHandler addPackage"); - aTest(@"PackageHandler sendFirstPackage"); + aTest(@"SdkClickHandler sendSdkClick"); [activityHandler setIadDate:date2 withPurchaseDate:nil]; [NSThread sleepForTimeInterval:1]; // iAdImpressionDate received - NSString * iAdImpressionDate2Log =[NSString stringWithFormat:@"iAdImpressionDate received: %@", date2]; + NSString * iAdImpressionDate2Log = [NSString stringWithFormat:@"iAdImpressionDate received: %@", date2]; aDebug(iAdImpressionDate2Log); // second iad package added - aTest(@"PackageHandler addPackage"); - aTest(@"PackageHandler sendFirstPackage"); + aTest(@"SdkClickHandler sendSdkClick"); // 1 session + 2 click packages - aiEquals(3, (int)[self.packageHandlerMock.packageQueue count]); + aiEquals(1, (int)[self.packageHandlerMock.packageQueue count]); + + aiEquals(2, (int)[self.sdkClickHandlerMock.packageQueue count]); // first iad package - ADJActivityPackage *firstIadPackage = (ADJActivityPackage *) self.packageHandlerMock.packageQueue[1]; + ADJActivityPackage *firstIadPackage = (ADJActivityPackage *) self.sdkClickHandlerMock.packageQueue[0]; // create activity package test ADJPackageFields * firstIadFields = [ADJPackageFields fields]; @@ -959,7 +1064,7 @@ - (void)testIadDates [self testClickPackage:firstIadPackage fields:firstIadFields source:@"iad"]; // second iad package - ADJActivityPackage * secondIadPackage = (ADJActivityPackage *) self.packageHandlerMock.packageQueue[2]; + ADJActivityPackage * secondIadPackage = (ADJActivityPackage *) self.sdkClickHandlerMock.packageQueue[1]; // create activity package test ADJPackageFields * secondIadFields = [ADJPackageFields fields]; @@ -976,17 +1081,10 @@ - (void)testIadDetails [self reset]; // create the config to start the session - ADJConfig * config = [ADJConfig configWithAppToken:@"123456789012" environment:ADJEnvironmentSandbox]; + ADJConfig * config = [self getConfig]; // start activity handler with config - id activityHandler = [ADJActivityHandler handlerWithConfig:config]; - - // it's necessary to sleep the activity for a while after each handler call - // to let the internal queue act - [NSThread sleepForTimeInterval:2.0]; - - // test init values and first session - [self checkInitAndFirstSession]; + id activityHandler =[self startAndCheckFirstSession:config]; // test iad details // should be ignored @@ -1041,15 +1139,15 @@ - (void)testIadDetails [activityHandler setIadDetails:attributionDetails error:nil retriesLeft:1]; [NSThread sleepForTimeInterval:2]; - aTest(@"PackageHandler addPackage"); - aTest(@"PackageHandler sendFirstPackage"); - + aTest(@"SdkClickHandler sendSdkClick"); // check the number of activity packages // 1 session + 1 sdk_click - aiEquals(2, (int)[self.packageHandlerMock.packageQueue count]); + aiEquals(1, (int)[self.packageHandlerMock.packageQueue count]); + + aiEquals(1, (int)[self.sdkClickHandlerMock.packageQueue count]); // get the click package - ADJActivityPackage *clickPackage = (ADJActivityPackage *) self.packageHandlerMock.packageQueue[1]; + ADJActivityPackage *clickPackage = (ADJActivityPackage *) self.sdkClickHandlerMock.packageQueue[0]; // create activity package test ADJPackageFields * clickPackageFields = [ADJPackageFields fields]; @@ -1060,12 +1158,42 @@ - (void)testIadDetails [self testClickPackage:clickPackage fields:clickPackageFields source:@"iad3"]; } +- (void)testSetDeviceToken { + // reseting to make the test order independent + [self reset]; + + // create the config to start the session + ADJConfig * config = [self getConfig]; + + // create handler and start the first session + id activityHandler = [self startAndCheckFirstSession:config]; + + const char bytes[] = "\xFC\x07\x21\xB6\xDF\xAD\x5E\xE1\x10\x97\x5B\xB2\xA2\x63\xDE\x00\x61\xCC\x70\x5B\x4A\x85\xA8\xAE\x3C\xCF\xBE\x7A\x66\x2F\xB1\xAB"; + [activityHandler setDeviceToken:[NSData dataWithBytes:bytes length:(sizeof(bytes) - 1)]]; + + [NSThread sleepForTimeInterval:1]; + + aTest(@"SdkClickHandler sendSdkClick"); + + // get the click package + ADJActivityPackage * deviceTokenClickPackage = (ADJActivityPackage *) self.sdkClickHandlerMock.packageQueue[0]; + + // create activity package test + ADJPackageFields * deviceTokenClickFields = [ADJPackageFields fields]; + + // and set it + deviceTokenClickFields.pushToken = @"fc0721b6dfad5ee110975bb2a263de0061cc705b4a85a8ae3ccfbe7a662fb1ab"; + + // test the first deeplink + [self testClickPackage:deviceTokenClickPackage fields:deviceTokenClickFields source:@"push"]; +} + - (void)testAttributionDelegate { // reseting to make the test order independent [self reset]; - ADJDelegateTest * delegateTests = [[ADJDelegateTest alloc] init]; + ADJAttributionChangedDelegate * delegateTests = [[ADJAttributionChangedDelegate alloc] init]; [self checkFinishTasks:delegateTests attributionDelegatePresent:YES @@ -1113,74 +1241,153 @@ - (void)testLaunchDeepLink [self reset]; // create the config to start the session - ADJConfig * config = [ADJConfig configWithAppToken:@"123456789012" environment:ADJEnvironmentProduction]; - - // set verbose log level - config.logLevel = ADJLogLevelDebug; + ADJConfig * config = [self getConfig:@"production" logLevel:ADJLogLevelVerbose appToken:@"qwerty123456"]; // start the session - id activityHandler =[ADJActivityHandler handlerWithConfig:config]; - - NSURL* attributions = [NSURL URLWithString:@"AdjustTests://example.com/path/inApp?adjust_tracker=trackerValue&other=stuff&adjust_campaign=campaignValue&adjust_adgroup=adgroupValue&adjust_creative=creativeValue"]; + id activityHandler = [self + getActivityHandler:config + logLevel:ADJLogLevelVerbose + isProductionEnvironment:YES + readActivityState:nil + readAttribution:nil + startEnabled:YES]; - [activityHandler appWillOpenUrl:attributions]; + [NSThread sleepForTimeInterval:2.0]; - [activityHandler trackEvent:[ADJEvent eventWithEventToken:@"abc123"]]; + [self checkInitTests:NO defaultTracker:nil startsSending:NO]; - [NSThread sleepForTimeInterval:3.0]; + [self startActivity:activityHandler]; - // test init values - [self checkInit:ADJEnvironmentProduction logLevel:@"6"]; + [NSThread sleepForTimeInterval:2.0]; - // test first session start [self checkFirstSession]; - // test success event response data - ADJActivityPackage * eventPackage = self.packageHandlerMock.packageQueue[2]; - ADJEventResponseData * eventSuccessResponseData = [ADJResponseData buildResponseData:eventPackage]; - eventSuccessResponseData.success = YES; - - NSString * deeplinkString = @"{\"deeplink\":\"wrongDeeplink://\"}"; - NSData * deeplinkData = [deeplinkString dataUsingEncoding:NSUTF8StringEncoding]; - NSError *error = nil; - NSException *exception = nil; + [activityHandler finishedTracking:nil]; - NSDictionary * deeplinkDictionary = [ADJUtil buildJsonDict:deeplinkData exceptionPtr:&exception errorPtr:&error]; + [NSThread sleepForTimeInterval:1.0]; - anNil(deeplinkDictionary); + // if the response is null + anTest(@"AttributionHandler checkAttributionResponse"); + anError(@"Unable to open deep link"); + anInfo(@"Open deep link"); // test success session response data ADJActivityPackage * sessionPackage = self.packageHandlerMock.packageQueue[0]; ADJSessionResponseData * sessionResponseData = [ADJResponseData buildResponseData:sessionPackage]; - sessionResponseData.jsonResponse = deeplinkDictionary; + sessionResponseData.jsonResponse = [self getDeeplinkDictionary]; [activityHandler launchSessionResponseTasks:sessionResponseData]; [NSThread sleepForTimeInterval:2.0]; - aInfo(@"Trying to open deep link (wrongDeeplink://)"); -} - -- (void)testUpdateAttribution -{ - // reseting to make the test order independent - [self reset]; + aInfo(@"Open deep link (wrongDeeplink://)"); - // create the config - ADJConfig * config = [ADJConfig configWithAppToken:@"123456789012" environment:ADJEnvironmentSandbox]; + // checking the default values of the first session package + // should only have one package + aiEquals(1, (int)[self.packageHandlerMock.packageQueue count]); - ADJDelegateTest * delegateTests = [[ADJDelegateTest alloc] init]; - [config setDelegate:delegateTests]; + ADJActivityPackage *activityPackage = (ADJActivityPackage *) self.packageHandlerMock.packageQueue[0]; - aDebug(@"Delegate implements adjustAttributionChanged"); + // create activity package test + ADJPackageFields * fields = [ADJPackageFields fields]; - // start the session - id activityHandler =[ADJActivityHandler handlerWithConfig:config]; + fields.environment = @"production"; + // set first session + [self testPackageSession:activityPackage fields:fields sessionCount:@"1"]; +} + +- (void)testNotLaunchDeeplinkCallback { + // reseting to make the test order independent + [self reset]; + + // create the config to start the session + ADJConfig * config = [self getConfig]; + + ADJDeeplinkNotLaunchDelegate * notLaunchDeeplinkDelegate = [[ADJDeeplinkNotLaunchDelegate alloc] init]; + + [config setDelegate:notLaunchDeeplinkDelegate]; + + // start activity handler with config + id activityHandler = [self startAndCheckFirstSession:config]; + + // get session response data + ADJActivityPackage * sessionPackage = self.packageHandlerMock.packageQueue[0]; + + // set package handler to respond with a valid attribution + ADJSessionResponseData * sessionResponseData = [ADJResponseData buildResponseData:sessionPackage]; + sessionResponseData.jsonResponse = [self getDeeplinkDictionary]; + + [activityHandler launchSessionResponseTasks:sessionResponseData]; [NSThread sleepForTimeInterval:2.0]; - // test init values - [self checkInitAndFirstSession]; + // deeplink to launch + aInfo(@"Open deep link (wrongDeeplink://)"); + + // deeplink to launch + aDebug(@"Launching in the background for testing"); + + // callback called + aTest(@"ADJDeeplinkNotLaunchDelegate adjustDeeplinkResponse not launch, wrongDeeplink://"); + + // but deeplink not launched + anError(@"Unable to open deep link"); +} + +- (void)testDeeplinkCallback +{ + // reseting to make the test order independent + [self reset]; + + // create the config to start the session + ADJConfig * config = [self getConfig]; + + ADJDeeplinkLaunchDelegate * launchDeeplinkDelegate = [[ADJDeeplinkLaunchDelegate alloc] init]; + + [config setDelegate:launchDeeplinkDelegate]; + + // start activity handler with config + id activityHandler = [self startAndCheckFirstSession:config]; + + // get session response data + ADJActivityPackage * sessionPackage = self.packageHandlerMock.packageQueue[0]; + + // set package handler to respond with a valid attribution + ADJSessionResponseData * sessionResponseData = [ADJResponseData buildResponseData:sessionPackage]; + sessionResponseData.jsonResponse = [self getDeeplinkDictionary]; + + [activityHandler launchSessionResponseTasks:sessionResponseData]; + + [NSThread sleepForTimeInterval:2.0]; + + // deeplink to launch + aInfo(@"Open deep link (wrongDeeplink://)"); + + // deeplink to launch + aDebug(@"Launching in the background for testing"); + + // callback called + aTest(@"ADJDeeplinkLaunchDelegate adjustDeeplinkResponse launch, wrongDeeplink://"); + + // and deeplink launched + aError(@"Unable to open deep link (wrongDeeplink://)"); +} + +- (void)testUpdateAttribution +{ + // reseting to make the test order independent + [self reset]; + + // create the config to start the session + ADJConfig * config = [self getConfig]; + + ADJAttributionChangedDelegate * attributionChangedDelegate = [[ADJAttributionChangedDelegate alloc] init]; + [config setDelegate:attributionChangedDelegate]; + + aDebug(@"Delegate implements adjustAttributionChanged"); + + // start activity handler with config + id activityHandler = [self startAndCheckFirstSession:config]; // check if Attribution is not created with nil ADJAttribution * nilAttribution = [[ADJAttribution alloc] initWithJsonDict:nil]; @@ -1198,11 +1405,6 @@ - (void)testUpdateAttribution aTrue([activityHandler updateAttribution:emptyAttribution]); aDebug(@"Wrote Attribution: tt:(null) tn:(null) net:(null) cam:(null) adg:(null) cre:(null) cl:(null)"); - // check that it did not launch a non existent delegate - // not possible to test in iOs - //[NSThread sleepForTimeInterval:1]; - //anTest(@"ADJDelegateTest adjustAttributionChanged"); - emptyAttribution = [[ADJAttribution alloc] initWithJsonDict:emptyJsonDictionary]; // test first session package @@ -1218,29 +1420,38 @@ - (void)testUpdateAttribution anDebug(@"Wrote Attribution"); // end session - [activityHandler trackSubsessionEnd]; + [self stopActivity:activityHandler]; [NSThread sleepForTimeInterval:2]; [self checkEndSession]; // create the new config - config = [ADJConfig configWithAppToken:@"123456789012" environment:ADJEnvironmentSandbox]; + config = [self getConfig]; // set delegate to see attribution launched - [config setDelegate:delegateTests]; + [config setDelegate:attributionChangedDelegate]; - id restartActivityHandler = [ADJActivityHandler handlerWithConfig:config]; + id restartActivityHandler = [self getActivityHandler:config + logLevel:ADJLogLevelInfo + isProductionEnvironment:NO + readActivityState:@"ec:0 sc:1 ssc:1" + readAttribution:@"tt:(null) tn:(null) net:(null) cam:(null) adg:(null) cre:(null) cl:(null)" + startEnabled:YES]; - [NSThread sleepForTimeInterval:3]; + [NSThread sleepForTimeInterval:2]; // test init values - [self checkInit:ADJEnvironmentSandbox - logLevel:@"3" - readActivityState:@"ec:0 sc:1 ssc:1" - readAttribution:@"tt:(null) tn:(null) net:(null) cam:(null) adg:(null) cre:(null) cl:(null)"]; + [self checkInitTests]; + + [self startActivity:restartActivityHandler]; + + [NSThread sleepForTimeInterval:2]; + - // test second subsession - [self checkSubsession:1 subSessionCount:2 timerAlreadyStarted:NO]; + ADJSessionState * firstRestart = [ADJSessionState sessionStateWithSessionType:ADJSessionTypeSubSession]; + firstRestart.subsessionCount = 2; + firstRestart.timerAlreadyStarted = NO; + [self checkStartInternal:firstRestart]; // check that it does not update the attribution after the restart aFalse([restartActivityHandler updateAttribution:emptyAttribution]); @@ -1266,17 +1477,14 @@ - (void)testUpdateAttribution ADJAttribution * firstAttribution = [[ADJAttribution alloc] initWithJsonDict:firstAttributionDictionary]; - sessionResponseDataWithAttribution.attribution = firstAttribution; //check that it updates + sessionResponseDataWithAttribution.attribution = firstAttribution; [restartActivityHandler launchSessionResponseTasks:sessionResponseDataWithAttribution]; [NSThread sleepForTimeInterval:1]; aDebug(@"Wrote Attribution: tt:ttValue tn:tnValue net:nValue cam:cpValue adg:aValue cre:ctValue cl:clValue"); - - // check that it launch the saved attribute - // not possible to test in iOs - //[NSThread sleepForTimeInterval:2]; - //aTest(@"ADJDelegateTest adjustAttributionChanged, tt:null tn:null net:null cam:null adg:null cre:null cl:null"); + aDebug(@"Launching in the background for testing"); + aTest(@"ADJAttributionChangedDelegate adjustAttributionChanged, tt:ttValue tn:tnValue net:nValue cam:cpValue adg:aValue cre:ctValue cl:clValue"); // test first session package ADJActivityPackage * attributionPackage = self.attributionHandlerMock.attributionPackage; @@ -1289,28 +1497,39 @@ - (void)testUpdateAttribution [NSThread sleepForTimeInterval:1]; anDebug(@"Wrote Attribution"); + anTest(@"ADJAttributionChangedDelegate adjustAttributionChanged"); // end session - [restartActivityHandler trackSubsessionEnd]; - [NSThread sleepForTimeInterval:2]; + [self stopActivity:restartActivityHandler]; + [NSThread sleepForTimeInterval:1]; [self checkEndSession]; // create the new config - config = [ADJConfig configWithAppToken:@"123456789012" environment:ADJEnvironmentSandbox]; + config = [self getConfig]; // set delegate to see attribution launched - [config setDelegate:delegateTests]; + [config setDelegate:attributionChangedDelegate]; - id secondRestartActivityHandler = [ADJActivityHandler handlerWithConfig:config]; + id secondRestartActivityHandler = [self getActivityHandler:config + logLevel:ADJLogLevelInfo + isProductionEnvironment:NO + readActivityState:@"ec:0 sc:1 ssc:2" + readAttribution:@"tt:ttValue tn:tnValue net:nValue cam:cpValue adg:aValue cre:ctValue cl:clValue" + startEnabled:YES]; - [NSThread sleepForTimeInterval:3]; + [NSThread sleepForTimeInterval:2]; - // test init values - [self checkInit:ADJEnvironmentSandbox logLevel:@"3" readActivityState:@"ec:0 sc:1 ssc:2" readAttribution:@"tt:ttValue tn:tnValue net:nValue cam:cpValue adg:aValue cre:ctValue cl:clValue"]; + [self checkInitTests]; + + [self startActivity:secondRestartActivityHandler]; + + [NSThread sleepForTimeInterval:2]; - // test third subsession - [self checkSubsession:1 subSessionCount:3 timerAlreadyStarted:NO]; + ADJSessionState * secondRestart = [ADJSessionState sessionStateWithSessionType:ADJSessionTypeSubSession]; + secondRestart.subsessionCount = 3; + secondRestart.timerAlreadyStarted = NO; + [self checkStartInternal:secondRestart]; // check that it does not update the attribution after the restart aFalse([secondRestartActivityHandler updateAttribution:firstAttribution]); @@ -1340,17 +1559,15 @@ - (void)testUpdateAttribution [NSThread sleepForTimeInterval:1]; aDebug(@"Wrote Attribution: tt:ttValue2 tn:tnValue2 net:nValue2 cam:cpValue2 adg:aValue2 cre:ctValue2 cl:clValue2"); - - // check that it launch the saved attribute - // not possible to test in iOs - //[NSThread sleepForTimeInterval:1]; - //aTest(@"onAttributionChanged: tt:ttValue2 tn:tnValue2 net:nValue2 cam:cpValue2 adg:aValue2 cre:ctValue2 cl:clValue2"); + aDebug(@"Launching in the background for testing"); + aTest(@"ADJAttributionChangedDelegate adjustAttributionChanged, tt:ttValue2 tn:tnValue2 net:nValue2 cam:cpValue2 adg:aValue2 cre:ctValue2 cl:clValue2"); // check that it does not update the attribution aFalse([secondRestartActivityHandler updateAttribution:secondAttribution]); anDebug(@"Wrote Attribution"); } + - (void)testOfflineMode { // reseting to make the test order independent @@ -1361,27 +1578,55 @@ - (void)testOfflineMode [ADJAdjustFactory setSubsessionInterval:(0.5)]; // 1/2 second // create the config to start the session - ADJConfig * config = [ADJConfig configWithAppToken:@"123456789012" environment:ADJEnvironmentSandbox]; + ADJConfig * config = [self getConfig]; // start activity handler with config - id activityHandler = [ADJActivityHandler handlerWithConfig:config]; + id activityHandler = [self getFirstActivityHandler:config]; // put SDK offline [activityHandler setOfflineMode:YES]; - [NSThread sleepForTimeInterval:3]; + ADJInternalState * internalState = [activityHandler internalState]; + + aTrue([internalState isOffline]); + + [NSThread sleepForTimeInterval:2]; + + // not writing activity state because it set enable does not start the sdk + anDebug(@"Wrote Activity state"); // check if message the disable of the SDK - aInfo(@"Pausing package and attribution handler to put in offline mode"); + aInfo(@"Package handler and attribution handler will start paused due to SDK being offline"); // test init values - [self checkInit:ADJEnvironmentSandbox logLevel:@"3"]; + [self checkInitTests:NO]; + + [self checkHandlerStatus:YES]; + + // start the sdk + // foreground timer does not start because it's paused + [self startActivity:activityHandler]; + + [NSThread sleepForTimeInterval:2]; // test first session start - [self checkFirstSession:YES]; + ADJSessionState * firstSessionStartPaused = [ADJSessionState sessionStateWithSessionType:ADJSessionTypeSession]; + firstSessionStartPaused.paused = YES; + firstSessionStartPaused.toSend = NO; + firstSessionStartPaused.foregroundTimerStarts = NO; + firstSessionStartPaused.foregroundTimerAlreadyStarted = NO; - // test end session logs - [self checkEndSession]; + // check session that is paused + [self checkStartInternal:firstSessionStartPaused]; + + [self stopActivity:activityHandler]; + + [NSThread sleepForTimeInterval:1]; + + // test end session of disable + [self checkEndSession:YES + updateActivityState:NO + eventBufferingEnabled:NO]; // disable the SDK [activityHandler setEnabled:NO]; @@ -1393,32 +1638,30 @@ - (void)testOfflineMode aDebug(@"Wrote Activity state: ec:0 sc:1 ssc:1"); // check if message the disable of the SDK - aInfo(@"Pausing package handler and attribution handler to disable the SDK"); + aInfo(@"Pausing package handler and attribution handler due to SDK being disabled"); [NSThread sleepForTimeInterval:1]; - // test end session logs - [self checkEndSession]; + [self checkHandlerStatus:YES]; // put SDK back online [activityHandler setOfflineMode:NO]; - aInfo(@"Package and attribution handler remain paused because the SDK is disabled"); + aInfo(@"Package and attribution handler remain paused due to SDK being disabled"); [NSThread sleepForTimeInterval:1]; - // doesn't pause if it was already paused - anTest(@"AttributionHandler pauseSending"); - anTest(@"PackageHandler pauseSending"); + // test the update status, still paused + [self checkHandlerStatusNotCalled]; // try to do activities while SDK disabled - [activityHandler trackSubsessionStart]; + [activityHandler applicationDidBecomeActive]; [activityHandler trackEvent:[ADJEvent eventWithEventToken:@"event1"]]; [NSThread sleepForTimeInterval:3]; // check that timer was not executed - [self checkTimerIsFired:NO]; + [self checkForegroundTimerFired:NO]; // check that it did not wrote activity state from new session or subsession anDebug(@"Wrote Activity state"); @@ -1426,6 +1669,13 @@ - (void)testOfflineMode // check that it did not add any package anTest(@"PackageHandler addPackage"); + // end the session + [self stopActivity:activityHandler]; + + [NSThread sleepForTimeInterval:1]; + + [self checkEndSession:NO]; + // enable the SDK again [activityHandler setEnabled:YES]; @@ -1434,11 +1684,25 @@ - (void)testOfflineMode [NSThread sleepForTimeInterval:1]; + aDebug(@"Wrote Activity state"); + // check that it re-enabled - aInfo(@"Resuming package handler and attribution handler to enabled the SDK"); + aInfo(@"Resuming package handler and attribution handler due to SDK being enabled"); + + [NSThread sleepForTimeInterval:1]; + + // it is still paused because it's on the background + [self checkHandlerStatus:YES]; + + [self startActivity:activityHandler]; + + [NSThread sleepForTimeInterval:1]; + + ADJSessionState * secondSessionState = [ADJSessionState sessionStateWithSessionType:ADJSessionTypeSession]; + secondSessionState.sessionCount = 2; // test that is not paused anymore - [self checkNewSession:NO sessionCount:2 eventCount:0]; + [self checkStartInternal:secondSessionState]; } - (void)testGetAttribution @@ -1447,25 +1711,27 @@ - (void)testGetAttribution [self reset]; // adjust the intervals for testing - [ADJAdjustFactory setTimerStart:0.5]; // 0.5 second + //[ADJAdjustFactory setTimerStart:0.5]; // 0.5 second [ADJAdjustFactory setSessionInterval:(4)]; // 4 second // create the config to start the session - ADJConfig * config = [ADJConfig configWithAppToken:@"123456789012" environment:ADJEnvironmentSandbox]; + ADJConfig * config = [self getConfig]; // set delegate - ADJDelegateTest * delegateTests = [[ADJDelegateTest alloc] init]; - [config setDelegate:delegateTests]; + ADJAttributionChangedDelegate * attributionChangedDelegate = [[ADJAttributionChangedDelegate alloc] init]; + [config setDelegate:attributionChangedDelegate]; // create handler and start the first session - id activityHandler = [ADJActivityHandler handlerWithConfig:config]; + id activityHandler = [self getFirstActivityHandler:config]; - // it's necessary to sleep the activity for a while after each handler call - // to let the internal queue act - [NSThread sleepForTimeInterval:3.0]; + [NSThread sleepForTimeInterval:2.0]; // test init values - [self checkInit:ADJEnvironmentSandbox logLevel:@"3"]; + [self checkInitTests]; + + [self startActivity:activityHandler]; + + [NSThread sleepForTimeInterval:2.0]; /*** * // if it' a new session @@ -1487,10 +1753,9 @@ - (void)testGetAttribution // -> Not called // test first session start - [self checkFirstSession]; - - // test that get attribution wasn't called - anTest(@"AttributionHandler getAttribution"); + ADJSessionState * newSessionState = [ADJSessionState sessionStateWithSessionType:ADJSessionTypeSession]; + newSessionState.getAttributionIsCalled = [NSNumber numberWithBool:NO]; + [self checkStartInternal:newSessionState]; // subsession count increased to 2 // attribution is still null, @@ -1498,10 +1763,10 @@ - (void)testGetAttribution // -> Called // trigger a new sub session - [activityHandler trackSubsessionStart]; + [activityHandler applicationDidBecomeActive]; [NSThread sleepForTimeInterval:2.0]; - [self checkSubsession:1 subSessionCount:2 timerAlreadyStarted:YES getAttributionIsCalled:YES]; + [self checkSubSession:1 subsessionCount:2 getAttributionIsCalled:YES]; // subsession count increased to 3 // attribution is still null, @@ -1513,10 +1778,10 @@ - (void)testGetAttribution aDebug(@"Wrote Activity state: ec:0 sc:1 ssc:2"); // trigger a new session - [activityHandler trackSubsessionStart]; + [self startActivity:activityHandler]; [NSThread sleepForTimeInterval:2.0]; - [self checkSubsession:1 subSessionCount:3 timerAlreadyStarted:YES getAttributionIsCalled:YES]; + [self checkSubSession:1 subsessionCount:3 getAttributionIsCalled:YES]; // subsession is reset to 1 with new session // attribution is still null, @@ -1524,10 +1789,10 @@ - (void)testGetAttribution // -> Not called [NSThread sleepForTimeInterval:3.0]; // 5 seconds = 2 + 3 - [activityHandler trackSubsessionStart]; + [activityHandler applicationDidBecomeActive]; [NSThread sleepForTimeInterval:2.0]; - [self checkSubsession:2 subSessionCount:1 timerAlreadyStarted:YES getAttributionIsCalled:NO]; + [self checkFurtherSessions:2 getAttributionIsCalled:NO]; // subsession count increased to 2 // attribution is set, @@ -1559,10 +1824,10 @@ - (void)testGetAttribution aDebug(@"Wrote Attribution: tt:ttValue tn:tnValue net:nValue cam:cpValue adg:aValue cre:ctValue cl:clValue"); // trigger a new sub session - [activityHandler trackSubsessionStart]; + [activityHandler applicationDidBecomeActive]; [NSThread sleepForTimeInterval:2.0]; - [self checkSubsession:2 subSessionCount:2 timerAlreadyStarted:YES getAttributionIsCalled:YES]; + [self checkSubSession:2 subsessionCount:2 getAttributionIsCalled:YES]; // subsession count is reset to 1 // attribution is set, @@ -1570,10 +1835,10 @@ - (void)testGetAttribution // -> Not called [NSThread sleepForTimeInterval:3.0]; // 5 seconds = 2 + 3 - [activityHandler trackSubsessionStart]; + [activityHandler applicationDidBecomeActive]; [NSThread sleepForTimeInterval:2.0]; - [self checkSubsession:3 subSessionCount:1 timerAlreadyStarted:YES getAttributionIsCalled:NO]; + [self checkFurtherSessions:3 getAttributionIsCalled:NO]; // subsession increased to 2 // attribution is set, @@ -1584,10 +1849,10 @@ - (void)testGetAttribution aDebug(@"Wrote Activity state: ec:0 sc:3 ssc:1"); // trigger a new sub session - [activityHandler trackSubsessionStart]; + [activityHandler applicationDidBecomeActive]; [NSThread sleepForTimeInterval:2.0]; - [self checkSubsession:3 subSessionCount:2 timerAlreadyStarted:YES getAttributionIsCalled:NO]; + [self checkSubSession:3 subsessionCount:2 getAttributionIsCalled:NO]; // subsession is reset to 1 // attribution is set, @@ -1595,58 +1860,207 @@ - (void)testGetAttribution // -> Not called [NSThread sleepForTimeInterval:3.0]; // 5 seconds = 2 + 3 - [activityHandler trackSubsessionStart]; + [activityHandler applicationDidBecomeActive]; [NSThread sleepForTimeInterval:2.0]; - [self checkSubsession:4 subSessionCount:1 timerAlreadyStarted:YES getAttributionIsCalled:NO]; + [self checkFurtherSessions:4 getAttributionIsCalled:NO]; } -- (void)testTimer +- (void)testForegroundTimer { // reseting to make the test order independent [self reset]; // change the timer defaults [ADJAdjustFactory setTimerInterval:4]; - [ADJAdjustFactory setTimerStart:0]; + [ADJAdjustFactory setTimerStart:4]; // create the config to start the session - ADJConfig * config = [ADJConfig configWithAppToken:@"123456789012" environment:ADJEnvironmentSandbox]; + ADJConfig * config = [self getConfig]; // create handler and start the first session -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wunused-variable" - id activityHandler = [ADJActivityHandler handlerWithConfig:config]; -#pragma clang diagnostic pop + id activityHandler = [self startAndCheckFirstSession:config]; + + // wait enough to fire the first cycle + [NSThread sleepForTimeInterval:1.0]; + + [self checkForegroundTimerFired:YES]; + + // end subsession to stop timer + [activityHandler applicationWillResignActive]; + + // don't wait enough for a new cycle + [NSThread sleepForTimeInterval:2]; + + // start a new session + [activityHandler applicationDidBecomeActive]; + + [NSThread sleepForTimeInterval:1]; + + // check that not enough time passed to fire again + [self checkForegroundTimerFired:NO]; + + // end subsession to stop timer + [activityHandler applicationWillResignActive]; + + // wait enough for a new cycle + [NSThread sleepForTimeInterval:6]; + + // start a new session + [activityHandler applicationDidBecomeActive]; + + [NSThread sleepForTimeInterval:1]; + + // check that enough time passed to fire again + [self checkForegroundTimerFired:YES]; +} + +- (void)testSendBackground { + // reseting to make the test order independent + [self reset]; + + [ADJAdjustFactory setTimerInterval:4]; + + // create the config to start the session + ADJConfig * config = [self getConfig]; + + // enable send in the background + [config setSendInBackground:YES]; + + // create activity handler without starting + id activityHandler = [self getFirstActivityHandler:config]; - // it's necessary to sleep the activity for a while after each handler call - // to let the internal queue act [NSThread sleepForTimeInterval:2.0]; - // test init values - [self checkInit:ADJEnvironmentSandbox logLevel:@"3"]; + // handlers start sending + [self checkInitTests:NO defaultTracker:nil startsSending:YES]; - // test first session start + [self startActivity:activityHandler]; + + [NSThread sleepForTimeInterval:2.0]; + + // test session [self checkFirstSession]; - // wait enough to fire the first cycle + // end subsession + // background timer starts + [self stopActivity:activityHandler]; + + [NSThread sleepForTimeInterval:1.0]; + + // session end does not pause the handlers + [self checkEndSession:NO + updateActivityState:YES + eventBufferingEnabled:NO + checkOnPause:YES +forgroundAlreadySuspended:NO + backgroundTimerStarts:YES]; + + // end subsession again + // to test if background timer starts again + [self stopActivity:activityHandler]; + + [NSThread sleepForTimeInterval:1.0]; + + // session end does not pause the handlers + [self checkEndSession:NO + updateActivityState:YES + eventBufferingEnabled:NO + checkOnPause:YES +forgroundAlreadySuspended:YES + backgroundTimerStarts:NO]; + + // wait for background timer launch [NSThread sleepForTimeInterval:3.0]; - [self checkTimerIsFired:YES]; + // background timer fired + aTest(@"PackageHandler sendFirstPackage"); - // end subsession to stop timer - //[activityHandler trackSubsessionEnd]; + // wait enough time + [NSThread sleepForTimeInterval:3.0]; - // wait enough for a new cycle - //[NSThread sleepForTimeInterval:6.0]; + // check that background timer does not fire again + anTest(@"PackageHandler sendFirstPackage"); + + [activityHandler trackEvent:[ADJEvent eventWithEventToken:@"abc123"]]; + + [NSThread sleepForTimeInterval:1.0]; + + // check that event package was added + aTest(@"PackageHandler addPackage"); + + // check that event was sent to package handler + aTest(@"PackageHandler sendFirstPackage"); + + // and not buffered + anInfo(@"Buffered event"); + + // does fire background timer + aVerbose(@"Background timer starting. Launching in 4.0 seconds"); + + // after tracking the event it should write the activity state + aDebug(@"Wrote Activity state"); + + // disable and enable the sdk while in the background + [activityHandler setEnabled:NO]; + + // check that it is disabled + aFalse([activityHandler isEnabled]); + + // check if message the disable of the SDK + aInfo(@"Pausing package handler and attribution handler due to SDK being disabled"); + + [NSThread sleepForTimeInterval:1.0]; + + // handlers being paused because of the disable + [self checkHandlerStatus:YES]; + + [activityHandler setEnabled:YES]; + + // check that it is enabled + aTrue([activityHandler isEnabled]); + + // check if message the enable of the SDK + aInfo(@"Resuming package handler and attribution handler due to SDK being enabled"); + + [NSThread sleepForTimeInterval:1.0]; + + // handlers being resumed because of the enable + // even in the background because of the sendInBackground option + [self checkHandlerStatus:NO]; + + // set offline and online the sdk while in the background + [activityHandler setOfflineMode:YES]; + + ADJInternalState * internalState = [activityHandler internalState]; + + // check that it is offline + aTrue([internalState isOffline]); + + // check if message the offline of the SDK + aInfo(@"Pausing package and attribution handler to put SDK offline mode"); + + [NSThread sleepForTimeInterval:1.0]; + + // handlers being paused because of the offline + [self checkHandlerStatus:YES]; - //[activityHandler trackSubsessionStart]; + [activityHandler setOfflineMode:NO]; + + // check that it is online + aTrue([internalState isOnline]); + + // check if message the online of the SDK + aInfo(@"Resuming package handler and attribution handler to put SDK in online mode"); - //[NSThread sleepForTimeInterval:1.0]; + [NSThread sleepForTimeInterval:1.0]; - //[self checkTimerIsFired:NO]; + // handlers being resumed because of the online + // even in the background because of the sendInBackground option + [self checkHandlerStatus:NO]; } + - (void)testConvertUniversalLink { // reseting to make the test order independent @@ -1776,42 +2190,119 @@ - (NSURL*)getUniversalLinkUrl:(NSString*)path stringWithFormat:@"https://[hash].ulink.adjust.com/ulink%@%@%@", path, query, fragment]]; } -- (void)checkInit { - [self checkInit:ADJEnvironmentSandbox logLevel:@"3"]; +- (void)checkForegroundTimerFired:(BOOL)timerFired +{ + // timer fired + if (timerFired) { + aVerbose(@"Foreground timer fired"); + } else { + anVerbose(@"Foreground timer fired"); + } } -- (void)checkInit:(NSString *)environment - logLevel:(NSString *)logLevel -{ - [self checkInit:environment logLevel:logLevel readActivityState:nil readAttribution:nil]; +- (void)checkInitTests { + [self checkInitTests:NO]; +} + +- (void)checkInitTests:(BOOL)startsSending { + [self checkInitTests:NO defaultTracker:nil startsSending:startsSending]; } -- (void)checkInit:(NSString *)environment - logLevel:(NSString *)logLevel -readActivityState:(NSString *)readActivityState - readAttribution:(NSString *)readAttribution +- (void)checkInitTests:(BOOL)eventBuffering + defaultTracker:(NSString *) defaultTracker + startsSending:(BOOL)startsSending { + // check event buffering + if (eventBuffering) { + aInfo(@"Event buffering is enabled"); + } else { + anInfo(@"Event buffering is enabled"); + } + + // check default tracker + if (defaultTracker != nil) { + NSString * defaultTrackerLog = [NSString stringWithFormat:@"Default tracker: '%@'", defaultTracker]; + aInfo(defaultTrackerLog); + } - // check environment level - if ([environment isEqualToString:ADJEnvironmentSandbox]) { - aAssert(@"SANDBOX: Adjust is running in Sandbox mode. Use this setting for testing. Don't forget to set the environment to `production` before publishing"); - } else if ([environment isEqualToString:ADJEnvironmentProduction]) { - aAssert(@"PRODUCTION: Adjust is running in Production mode. Use this setting only for the build that you want to publish. Set the environment to `sandbox` if you want to test your app!"); + if (startsSending) { + aTest(@"PackageHandler initWithActivityHandler, startsSending: 1"); + aTest(@"AttributionHandler initWithActivityHandler, startsSending: 1"); + aTest(@"SdkClickHandler initWithStartsSending, startsSending: 1"); } else { - aFail(); + aTest(@"PackageHandler initWithActivityHandler, startsSending: 0"); + aTest(@"AttributionHandler initWithActivityHandler, startsSending: 0"); + aTest(@"SdkClickHandler initWithStartsSending, startsSending: 0"); } +} - // check log level - aTest([@"ADJLogger setLogLevel: " stringByAppendingString:logLevel]); +- (void)checkEndSession { + [self checkEndSession:YES]; +} - // check read files - [self checkReadFiles:readActivityState readAttribution:readAttribution]; +- (void)checkEndSession:(BOOL)updateActivityState { + [self checkEndSession:YES + updateActivityState:updateActivityState + eventBufferingEnabled:NO + checkOnPause:NO +forgroundAlreadySuspended:NO + backgroundTimerStarts:NO]; +} - // tries to read iad v3 - aDebug(@"iAd with 2 tries to read v3"); +- (void)checkEndSession:(BOOL)pausing + updateActivityState:(BOOL)updateActivityState + eventBufferingEnabled:(BOOL)eventBufferingEnabled +{ + [self checkEndSession:pausing + updateActivityState:updateActivityState + eventBufferingEnabled:eventBufferingEnabled + checkOnPause:NO +forgroundAlreadySuspended:NO + backgroundTimerStarts:NO]; +} - // iad is enabled - aDebug(@"ADJUST_NO_IAD or TARGET_OS_TV not set"); +- (void)checkEndSession:(BOOL)pausing + updateActivityState:(BOOL)updateActivityState + eventBufferingEnabled:(BOOL)eventBufferingEnabled + checkOnPause:(BOOL)checkOnPause +forgroundAlreadySuspended:(BOOL)forgroundAlreadySuspended + backgroundTimerStarts:(BOOL)backgroundTimerStarts +{ + if (checkOnPause) { + [self checkOnPause:forgroundAlreadySuspended + backgroundTimerStarts:backgroundTimerStarts]; + } + + if (pausing) { + [self checkHandlerStatus:YES eventBufferingIsEnabled:eventBufferingEnabled]; + } + + if (updateActivityState) { + aDebug(@"Wrote Activity state: "); + } else { + anDebug(@"Wrote Activity state: "); + } +} + +- (void) checkOnPause:(BOOL)forgroundAlreadySuspended +backgroundTimerStarts:(BOOL)backgroundTimerStarts +{ + // stop foreground timer + if (forgroundAlreadySuspended) { + aVerbose(@"Foreground timer is already suspended"); + } else { + aVerbose(@"Foreground timer suspended"); + } + + // start background timer + if (backgroundTimerStarts) { + aVerbose(@"Background timer starting."); + } else { + anVerbose(@"Background timer starting."); + } + + // starts the subsession + aVerbose(@"Subsession end"); } - (void)checkReadFiles:(NSString *)readActivityState @@ -1830,132 +2321,287 @@ - (void)checkReadFiles:(NSString *)readActivityState } } -- (void)checkFirstSession:(BOOL)paused +- (ADJConfig *)getConfig { + return [self getConfig:0]; +} +- (ADJConfig *)getConfig:(ADJLogLevel)logLevel { - // test if package handler started paused - if (paused) { - aTest(@"PackageHandler initWithActivityHandler, paused: 1"); - } else { - aTest(@"PackageHandler initWithActivityHandler, paused: 0"); + return [self getConfig:@"sandbox" logLevel:logLevel appToken:@"qwerty123456"]; +} + +- (ADJConfig *)getConfig:(NSString *)environment + logLevel:(ADJLogLevel)logLevel + appToken:(NSString *)appToken +{ + ADJConfig * config = [ADJConfig configWithAppToken:appToken environment:environment]; + + if (config != nil) { + if ([environment isEqualToString:ADJEnvironmentSandbox]) { + aAssert(@"SANDBOX: Adjust is running in Sandbox mode. Use this setting for testing. Don't forget to set the environment to `production` before publishing"); + } else if ([environment isEqualToString:ADJEnvironmentProduction]) { + aAssert(@"PRODUCTION: Adjust is running in Production mode. Use this setting only for the build that you want to publish. Set the environment to `sandbox` if you want to test your app!"); + } else { + aFail(); + } + + if (logLevel != 0) { + [config setLogLevel:logLevel]; + } } - [self checkNewSession:paused - sessionCount:1 - eventCount:0 - timerAlreadyStarted:NO]; + return config; } -- (void)checkFirstSession +- (id) startAndCheckFirstSession:(ADJConfig *)config { - [self checkFirstSession:NO]; + return [self startAndCheckFirstSession:config logLevel:ADJLogLevelInfo]; } - -- (void)checkInitAndFirstSession +- (id) startAndCheckFirstSession:(ADJConfig *)config + logLevel:(ADJLogLevel)logLevel { - [self checkInit]; + // start activity handler with config + id activityHandler = [self getFirstActivityHandler:config logLevel:logLevel]; + + [NSThread sleepForTimeInterval:2.0]; + + // test init values + [self checkInitTests]; + + [self startActivity:activityHandler]; + + [NSThread sleepForTimeInterval:2.0]; + + // test session [self checkFirstSession]; + + return activityHandler; +} + +- (void)startActivity:(id)activityHandler { + // start activity + [activityHandler applicationDidBecomeActive]; + + ADJInternalState * internalState = [activityHandler internalState]; + + // comes to the foreground + aTrue([internalState isForeground]); +} + +- (void)stopActivity:(id)activityHandler { + // stop activity + [activityHandler applicationWillResignActive]; + + ADJInternalState * internalState = [activityHandler internalState]; + + // goes to the background + aTrue([internalState isBackground]); } -- (void)checkNewSession:(BOOL)paused - sessionCount:(int)sessionCount - eventCount:(int)eventCount +- (id) getFirstActivityHandler:(ADJConfig *)config { - [self checkNewSession:paused sessionCount:sessionCount eventCount:eventCount timerAlreadyStarted:NO]; + return [self getFirstActivityHandler:config logLevel:ADJLogLevelInfo]; } -- (void)checkNewSession:(BOOL)paused - sessionCount:(int)sessionCount - eventCount:(int)eventCount - timerAlreadyStarted:(BOOL)timerAlreadyStarted + +- (id) getFirstActivityHandler:(ADJConfig *)config + logLevel:(ADJLogLevel)logLevel { - // when a session package is being sent the attribution handler should resume sending - if (paused) { - aTest(@"AttributionHandler pauseSending"); - } else { - aTest(@"AttributionHandler resumeSending"); - } + return [self getActivityHandler:config + logLevel:logLevel + isProductionEnvironment:NO + readActivityState:nil + readAttribution:nil + startEnabled:YES]; +} - // when a session package is being sent the package handler should resume sending - if (paused) { - aTest(@"PackageHandler pauseSending"); - } else { - aTest(@"PackageHandler resumeSending"); - } +- (id) getActivityHandler:(ADJConfig *)config + logLevel:(ADJLogLevel)logLevel + isProductionEnvironment:(BOOL)isProductionEnvironment + readActivityState:(NSString *)readActivityState + readAttribution:(NSString *)readAttribution + startEnabled:(BOOL)startEnabled +{ + id activityHandler = [ADJActivityHandler handlerWithConfig:config]; - // if the package was build, it was sent to the Package Handler - aTest(@"PackageHandler addPackage"); + if (activityHandler != nil) { + // check log level + NSString * logMessage; + if (isProductionEnvironment) { + logMessage = [NSString stringWithFormat:@"ADJLogger setLogLevel: %d", ADJLogLevelAssert]; + } else { + logMessage = [NSString stringWithFormat:@"ADJLogger setLogLevel: %d", logLevel]; + } + aTest(logMessage); + + // check if files are read in constructor + [self checkReadFiles:readActivityState readAttribution:readAttribution]; + + ADJInternalState * internalState = [activityHandler internalState]; + // test default values + aiEquals(startEnabled, [internalState isEnabled]); + aTrue([internalState isOnline]); + aTrue([internalState isBackground]); + } - // after adding, the activity handler ping the Package handler to send the package - aTest(@"PackageHandler sendFirstPackage"); + return activityHandler; +} - // after sending a package saves the activity state - NSString * aStateWrote = [NSString stringWithFormat:@"Wrote Activity state: ec:%d sc:%d ssc:1", eventCount, sessionCount]; - aDebug(aStateWrote); +- (void)checkStartDisable { + anTest(@"AttributionHandler resumeSending"); + anTest(@"PackageHandler resumeSending"); + anTest(@"SdkClickHandler resumeSending"); + anTest(@"AttributionHandler pauseSending"); + anTest(@"PackageHandler pauseSending"); + anTest(@"SdkClickHandler pauseSending"); + anTest(@"PackageHandler addPackage"); + anTest(@"PackageHandler sendFirstPackage"); + anVerbose(@"Started subsession"); + anVerbose(@"Time span since last activity too short for a new subsession"); + anError(@"Time travel!"); + anDebug(@"Wrote Activity state: "); + anTest(@"AttributionHandler getAttribution"); + [self checkForegroundTimerFired:NO]; +} - [self checkTimerIsFired:!(paused || timerAlreadyStarted)]; +- (void)checkFirstSession { + ADJSessionState * sesssionState = [ADJSessionState sessionStateWithSessionType:ADJSessionTypeSession]; + [self checkStartInternal:sesssionState]; } -- (void)checkSubsession:(int)sessionCount - subSessionCount:(int)subsessionCount - timerAlreadyStarted:(BOOL)timerAlreadyStarted +- (void)checkSubSession:(NSInteger)sessionCount + subsessionCount:(NSInteger)subsessionCount getAttributionIsCalled:(BOOL)getAttributionIsCalled { - [self checkSubsession:sessionCount subSessionCount:subsessionCount]; + ADJSessionState * subSessionState = [ADJSessionState sessionStateWithSessionType:ADJSessionTypeSubSession]; - if (getAttributionIsCalled) { - aTest(@"AttributionHandler getAttribution"); - } else { - anTest(@"AttributionHandler getAttribution"); - } + subSessionState.sessionCount = sessionCount; + subSessionState.subsessionCount = subsessionCount; + subSessionState.getAttributionIsCalled = [NSNumber numberWithBool:getAttributionIsCalled]; + subSessionState.foregroundTimerAlreadyStarted = YES; + [self checkStartInternal:subSessionState]; - [self checkTimerIsFired:!timerAlreadyStarted]; } -- (void)checkSubsession:(int)sessionCount - subSessionCount:(int)subsessionCount - timerAlreadyStarted:(BOOL)timerAlreadyStarted +- (void)checkFurtherSessions:(NSInteger)sessionCount + getAttributionIsCalled:(BOOL)getAttributionIsCalled { - [self checkSubsession:sessionCount subSessionCount:subsessionCount]; + ADJSessionState * furtherSessionState = [ADJSessionState sessionStateWithSessionType:ADJSessionTypeSession]; - [self checkTimerIsFired:!timerAlreadyStarted]; + furtherSessionState.sessionCount = sessionCount; + furtherSessionState.timerAlreadyStarted = YES; + furtherSessionState.getAttributionIsCalled = [NSNumber numberWithBool:getAttributionIsCalled]; + furtherSessionState.foregroundTimerAlreadyStarted = YES; + + [self checkStartInternal:furtherSessionState]; } -- (void)checkSubsession:(int)sessionCount - subSessionCount:(int)subsessionCount -{ - // test the new sub session - aTest(@"PackageHandler resumeSending"); - // save activity state - NSString * aStateWrote = [NSString stringWithFormat:@"Wrote Activity state: ec:0 sc:%d ssc:%d", sessionCount, subsessionCount]; - aDebug(aStateWrote); - //aDebug(@"Wrote Activity state: ec:0 sc:1 ssc:2"); +- (void)checkStartInternal:(ADJSessionState *)sessionState { + // check applicationDidBecomeActive + [self checkApplicationDidBecomeActive:sessionState]; + + // update Handlers Status + [self checkHandlerStatus:!sessionState.toSend + eventBufferingIsEnabled:sessionState.eventBufferingIsEnabled]; + + // process Session + switch (sessionState.sessionType) { + case ADJSessionTypeSession: + { + // if the package was build, it was sent to the Package Handler + aTest(@"PackageHandler addPackage"); + + // after adding, the activity handler ping the Package handler to send the package + aTest(@"PackageHandler sendFirstPackage"); + break; + } + case ADJSessionTypeSubSession: + { + // test the subsession message + NSString * startedSubsessionLog = [NSString stringWithFormat:@"Started subsession %ld of session %ld", + sessionState.subsessionCount, sessionState.sessionCount]; + aVerbose(startedSubsessionLog); + break; + } + case ADJSessionTypeNonSession: + { + // stopped for a short time, not enough for a new sub subsession + aVerbose(@"Time span since last activity too short for a new subsession"); + break; + } + case ADJSessionTypeTimeTravel: + { + aError(@"Time travel!"); + break; + } + } - if (subsessionCount > 1) { - // test the subsession message - NSString * subsessionStarted = [NSString stringWithFormat:@"Started subsession %d of session %d", subsessionCount, sessionCount]; - aInfo(subsessionStarted); + // after processing the session, writes the activity state + if (sessionState.sessionType != ADJSessionTypeNonSession) { + NSString * wroteActivityLog = [NSString stringWithFormat:@"Wrote Activity state: ec:%ld sc:%ld ssc:%ld", + sessionState.eventCount, sessionState.sessionCount, sessionState.subsessionCount]; + aDebug(wroteActivityLog); + } + // check Attribution State + if (sessionState.getAttributionIsCalled != nil) { + if ([sessionState.getAttributionIsCalled boolValue]) { + aTest(@"AttributionHandler getAttribution"); + } else { + anTest(@"AttributionHandler getAttribution"); + } + } +} + +- (void)checkApplicationDidBecomeActive:(ADJSessionState *)sessionState { + // stops background timer + aVerbose(@"Background timer canceled"); + + // start foreground timer + if (sessionState.foregroundTimerStarts) { + if (sessionState.foregroundTimerAlreadyStarted) { + aVerbose(@"Foreground timer is already started"); + } else { + aVerbose(@"Foreground timer starting"); + } } else { - // test the subsession message - anInfo(@"Started subsession "); + anVerbose(@"Foreground timer is already started"); + anVerbose(@"Foreground timer starting"); } + + // starts the subsession + aVerbose(@"Subsession start"); } -- (void)checkEndSession +- (void)checkHandlerStatus:(BOOL)pausing { - aTest(@"PackageHandler pauseSending"); - aTest(@"AttributionHandler pauseSending"); - aDebug(@"Wrote Activity state:"); + [self checkHandlerStatus:pausing eventBufferingIsEnabled:NO]; } -- (void)checkTimerIsFired:(BOOL)timerFired +- (void)checkHandlerStatus:(BOOL)pausing + eventBufferingIsEnabled:(BOOL)eventBufferingIsEnabled { - if(timerFired) { - aDebug(@"Session timer fired"); + if (pausing) { + aTest(@"AttributionHandler pauseSending"); + aTest(@"PackageHandler pauseSending"); + aTest(@"SdkClickHandler pauseSending"); } else { - anDebug(@"Session timer fired"); + aTest(@"AttributionHandler resumeSending"); + aTest(@"PackageHandler resumeSending"); + aTest(@"SdkClickHandler resumeSending"); + if (!eventBufferingIsEnabled) { + aTest(@"PackageHandler sendFirstPackage"); + } } } +- (void)checkHandlerStatusNotCalled +{ + anTest(@"AttributionHandler pauseSending"); + anTest(@"PackageHandler pauseSending"); + anTest(@"SdkClickHandler pauseSending"); + anTest(@"AttributionHandler resumeSending"); + anTest(@"PackageHandler resumeSending"); + anTest(@"SdkClickHandler resumeSending"); +} - (void)checkFinishTasks:(NSObject *)delegateTest attributionDelegatePresent:(BOOL)attributionDelegatePresent @@ -1965,7 +2611,7 @@ - (void)checkFinishTasks:(NSObject *)delegateTest sessionFailureDelegatePresent:(BOOL)sessionFailureDelegatePresent { // create the config to start the session - ADJConfig * config = [ADJConfig configWithAppToken:@"123456789012" environment:ADJEnvironmentSandbox]; + ADJConfig * config = [self getConfig]; // set delegate [config setDelegate:delegateTest]; @@ -1997,14 +2643,24 @@ - (void)checkFinishTasks:(NSObject *)delegateTest } // create handler and start the first session - id activityHandler = [ADJActivityHandler handlerWithConfig:config]; - - [NSThread sleepForTimeInterval:2.0]; - - [self checkInitAndFirstSession]; + id activityHandler = [self startAndCheckFirstSession:config]; // test first session package ADJActivityPackage * firstSessionPackage = self.packageHandlerMock.packageQueue[0]; + + // create activity package test + ADJPackageFields * firstSessionPackageFields = [ADJPackageFields fields]; + + firstSessionPackageFields.hasResponseDelegate = + attributionDelegatePresent || + eventFailureDelegatePresent || + eventSuccessDelegatePresent || + sessionFailureDelegatePresent || + sessionSuccessDelegatePresent; + + // test first session + [self testPackageSession:firstSessionPackage fields:firstSessionPackageFields sessionCount:@"1"]; + // simulate a successful session ADJSessionResponseData * successSessionResponseData = [ADJResponseData buildResponseData:firstSessionPackage]; successSessionResponseData.success = YES; @@ -2024,6 +2680,8 @@ - (void)checkFinishTasks:(NSObject *)delegateTest // if present, the first session triggers the success session delegate if (sessionSuccessDelegatePresent) { aDebug(@"Launching success session tracking delegate"); + aDebug(@"Launching in the background for testing"); + aTest(@"ADJTrackingSucceededDelegate adjustSessionTrackingSucceeded"); } else { anDebug(@"Launching success session tracking delegate"); } @@ -2043,6 +2701,8 @@ - (void)checkFinishTasks:(NSObject *)delegateTest // if present, the first session triggers the failure session delegate if (sessionFailureDelegatePresent) { aDebug(@"Launching failed session tracking delegate"); + aDebug(@"Launching in the background for testing"); + aTest(@"ADJTrackingFailedDelegate adjustSessionTrackingFailed"); } else { anDebug(@"Launching failed session tracking delegate"); } @@ -2064,6 +2724,8 @@ - (void)checkFinishTasks:(NSObject *)delegateTest // if present, the success event triggers the success event delegate if (eventSuccessDelegatePresent) { aDebug(@"Launching success event tracking delegate"); + aDebug(@"Launching in the background for testing"); + aTest(@"ADJTrackingSucceededDelegate adjustEventTrackingSucceeded"); } else { anDebug(@"Launching success event tracking delegate"); } @@ -2083,6 +2745,8 @@ - (void)checkFinishTasks:(NSObject *)delegateTest // if present, the failure event triggers the failure event delegate if (eventFailureDelegatePresent) { aDebug(@"Launching failed event tracking delegate"); + aDebug(@"Launching in the background for testing"); + aTest(@"ADJTrackingFailedDelegate adjustEventTrackingFailed"); } else { anDebug(@"Launching failed event tracking delegate"); } @@ -2096,11 +2760,10 @@ - (void)checkFinishTasks:(NSObject *)delegateTest [NSThread sleepForTimeInterval:1.0]; - aTest(@"PackageHandler addPackage"); - aTest(@"PackageHandler sendFirstPackage"); + aTest(@"SdkClickHandler sendSdkClick"); // test sdk_click response data - ADJActivityPackage * sdkClickPackage = self.packageHandlerMock.packageQueue[2]; + ADJActivityPackage * sdkClickPackage = self.sdkClickHandlerMock.packageQueue[0]; ADJClickResponseData * sdkClickResponseData = [ADJResponseData buildResponseData:sdkClickPackage]; [activityHandler finishedTracking:sdkClickResponseData]; @@ -2112,4 +2775,18 @@ - (void)checkFinishTasks:(NSObject *)delegateTest anDebug(@"Launching success event tracking delegate"); anDebug(@"Launching failed event tracking delegate"); } + +- (NSDictionary *)getDeeplinkDictionary { + // set package handler to respond with a valid attribution + NSString * deeplinkString = @"{\"deeplink\":\"wrongDeeplink://\"}"; + NSData * deeplinkData = [deeplinkString dataUsingEncoding:NSUTF8StringEncoding]; + NSError *error = nil; + NSException *exception = nil; + + NSDictionary * deeplinkDictionary = [ADJUtil buildJsonDict:deeplinkData exceptionPtr:&exception errorPtr:&error]; + + anNil(deeplinkDictionary); + + return deeplinkDictionary; +} @end diff --git a/AdjustTests/ADJDelegateTest.h b/AdjustTests/ADJAttributionChangedDelegate.h similarity index 74% rename from AdjustTests/ADJDelegateTest.h rename to AdjustTests/ADJAttributionChangedDelegate.h index 35b223ee6..ef83c376c 100644 --- a/AdjustTests/ADJDelegateTest.h +++ b/AdjustTests/ADJAttributionChangedDelegate.h @@ -9,6 +9,6 @@ #import #import "Adjust.h" -@interface ADJDelegateTest : NSObject +@interface ADJAttributionChangedDelegate : NSObject @end diff --git a/AdjustTests/ADJDelegateTest.m b/AdjustTests/ADJAttributionChangedDelegate.m similarity index 77% rename from AdjustTests/ADJDelegateTest.m rename to AdjustTests/ADJAttributionChangedDelegate.m index 4c2701c7d..bb16759a2 100644 --- a/AdjustTests/ADJDelegateTest.m +++ b/AdjustTests/ADJAttributionChangedDelegate.m @@ -6,19 +6,19 @@ // Copyright (c) 2014 adjust GmbH. All rights reserved. // -#import "ADJDelegateTest.h" +#import "ADJAttributionChangedDelegate.h" #import "ADJLoggerMock.h" #import "ADJAdjustFactory.h" -static NSString * const prefix = @"ADJDelegateTest "; +static NSString * const prefix = @"ADJAttributionChangedDelegate "; -@interface ADJDelegateTest() +@interface ADJAttributionChangedDelegate() @property (nonatomic, strong) ADJLoggerMock *loggerMock; @end -@implementation ADJDelegateTest +@implementation ADJAttributionChangedDelegate - (id) init { self = [super init]; diff --git a/AdjustTests/ADJAttributionHandlerMock.m b/AdjustTests/ADJAttributionHandlerMock.m index 6d1adf586..32f4d95d4 100644 --- a/AdjustTests/ADJAttributionHandlerMock.m +++ b/AdjustTests/ADJAttributionHandlerMock.m @@ -15,7 +15,7 @@ @interface ADJAttributionHandlerMock() @property (nonatomic, strong) ADJLoggerMock *loggerMock; -@property (nonatomic, assign) BOOL startPaused; +@property (nonatomic, assign) BOOL startsSending; @property (nonatomic, assign) BOOL hasDelegate; @end @@ -25,18 +25,18 @@ @implementation ADJAttributionHandlerMock - (id)initWithActivityHandler:(id) activityHandler withAttributionPackage:(ADJActivityPackage *) attributionPackage - startPaused:(BOOL)startPaused + startsSending:(BOOL)startsSending hasAttributionChangedDelegate:(BOOL)hasDelegate { self = [super init]; if (self == nil) return nil; - self.startPaused = startPaused; + self.startsSending = startsSending; self.hasDelegate = hasDelegate; self.loggerMock = (ADJLoggerMock *) [ADJAdjustFactory logger]; self.attributionPackage = attributionPackage; - [self.loggerMock test:[prefix stringByAppendingFormat:@"initWithActivityHandler"]]; + [self.loggerMock test:[prefix stringByAppendingFormat:@"initWithActivityHandler, startsSending: %d", startsSending]]; return self; } diff --git a/AdjustTests/ADJAttributionHandlerTests.m b/AdjustTests/ADJAttributionHandlerTests.m index 3ca7ea470..d79337882 100644 --- a/AdjustTests/ADJAttributionHandlerTests.m +++ b/AdjustTests/ADJAttributionHandlerTests.m @@ -52,7 +52,7 @@ - (void)reset { self.loggerMock = [[ADJLoggerMock alloc] init]; [ADJAdjustFactory setLogger:self.loggerMock]; - ADJConfig * config = [ADJConfig configWithAppToken:@"123456789012" environment:ADJEnvironmentSandbox]; + ADJConfig * config = [ADJConfig configWithAppToken:@"qwerty123456" environment:ADJEnvironmentSandbox]; self.activityHandlerMock = [[ADJActivityHandlerMock alloc] initWithConfig:config]; [self savePackages:config]; @@ -74,7 +74,8 @@ - (void)savePackages:(ADJConfig *)config { [ADJTestsUtil deleteFile:@"AdjustIoActivityState" logger:self.loggerMock]; [ADJTestsUtil deleteFile:@"AdjustIoAttribution" logger:self.loggerMock]; - [ADJActivityHandler handlerWithConfig:config]; + id activityHandler = [ADJActivityHandler handlerWithConfig:config]; + [activityHandler applicationDidBecomeActive]; [NSThread sleepForTimeInterval:5.0]; ADJActivityPackage * attributionPackage = attributionHandlerMock.attributionPackage; @@ -95,7 +96,7 @@ - (void)testGetAttribution // reseting to make the test order independent [self reset]; - id attributionHandler = [ADJAttributionHandler handlerWithActivityHandler:self.activityHandlerMock withAttributionPackage:self.attributionPackage startPaused:NO hasAttributionChangedDelegate:YES]; + id attributionHandler = [ADJAttributionHandler handlerWithActivityHandler:self.activityHandlerMock withAttributionPackage:self.attributionPackage startsSending:YES hasAttributionChangedDelegate:YES]; // test null response without error [self checkGetAttributionResponse:attributionHandler responseType:ADJSessionResponseTypeNil]; @@ -156,7 +157,7 @@ - (void)testCheckSessionResponse // reseting to make the test order independent [self reset]; - id attributionHandler = [ADJAttributionHandler handlerWithActivityHandler:self.activityHandlerMock withAttributionPackage:self.attributionPackage startPaused:NO hasAttributionChangedDelegate:YES]; + id attributionHandler = [ADJAttributionHandler handlerWithActivityHandler:self.activityHandlerMock withAttributionPackage:self.attributionPackage startsSending:YES hasAttributionChangedDelegate:YES]; NSMutableDictionary * attributionDictionary = [[NSMutableDictionary alloc] init]; [attributionDictionary setObject:@"ttValue" forKey:@"tracker_token"]; @@ -194,7 +195,7 @@ - (void)testAskIn // reseting to make the test order independent [self reset]; - id attributionHandler = [ADJAttributionHandler handlerWithActivityHandler:self.activityHandlerMock withAttributionPackage:self.attributionPackage startPaused:NO hasAttributionChangedDelegate:YES]; + id attributionHandler = [ADJAttributionHandler handlerWithActivityHandler:self.activityHandlerMock withAttributionPackage:self.attributionPackage startsSending:YES hasAttributionChangedDelegate:YES]; NSMutableDictionary * askIn4sDictionary = [[NSMutableDictionary alloc] init]; [askIn4sDictionary setObject:@"4000" forKey:@"ask_in"]; @@ -258,7 +259,7 @@ - (void)testPause // reseting to make the test order independent [self reset]; - id attributionHandler = [ADJAttributionHandler handlerWithActivityHandler:self.activityHandlerMock withAttributionPackage:self.attributionPackage startPaused:YES hasAttributionChangedDelegate:YES]; + id attributionHandler = [ADJAttributionHandler handlerWithActivityHandler:self.activityHandlerMock withAttributionPackage:self.attributionPackage startsSending:NO hasAttributionChangedDelegate:YES]; [NSURLSession setResponseType:ADJSessionResponseTypeMessage]; @@ -280,7 +281,7 @@ - (void)testWithoutListener // reseting to make the test order independent [self reset]; - id attributionHandler = [ADJAttributionHandler handlerWithActivityHandler:self.activityHandlerMock withAttributionPackage:self.attributionPackage startPaused:NO hasAttributionChangedDelegate:NO]; + id attributionHandler = [ADJAttributionHandler handlerWithActivityHandler:self.activityHandlerMock withAttributionPackage:self.attributionPackage startsSending:YES hasAttributionChangedDelegate:NO]; [NSURLConnection setResponseType:ADJResponseTypeMessage]; diff --git a/AdjustTests/ADJDeeplinkDelegate.h b/AdjustTests/ADJDeeplinkDelegate.h new file mode 100644 index 000000000..c7525b2a8 --- /dev/null +++ b/AdjustTests/ADJDeeplinkDelegate.h @@ -0,0 +1,18 @@ +// +// ADJDeeplinkDelegate.h +// Adjust +// +// Created by Pedro Filipe on 13/05/16. +// Copyright © 2016 adjust GmbH. All rights reserved. +// + +#import +#import "Adjust.h" + +@interface ADJDeeplinkLaunchDelegate : NSObject + +@end + +@interface ADJDeeplinkNotLaunchDelegate : NSObject + +@end diff --git a/AdjustTests/ADJDeeplinkDelegate.m b/AdjustTests/ADJDeeplinkDelegate.m new file mode 100644 index 000000000..11df4fe00 --- /dev/null +++ b/AdjustTests/ADJDeeplinkDelegate.m @@ -0,0 +1,62 @@ +// +// ADJDeeplinkDelegate.m +// Adjust +// +// Created by Pedro Filipe on 13/05/16. +// Copyright © 2016 adjust GmbH. All rights reserved. +// + +#import "ADJDeeplinkDelegate.h" +#import "ADJLoggerMock.h" +#import "ADJAdjustFactory.h" + +static NSString * const launchPrefix = @"ADJDeeplinkLaunchDelegate "; +static NSString * const notLaunchPrefix = @"ADJDeeplinkNotLaunchDelegate "; + +@interface ADJDeeplinkLaunchDelegate() +@property (nonatomic, strong) ADJLoggerMock *loggerMock; +@end + +@implementation ADJDeeplinkLaunchDelegate + +- (id) init { + self = [super init]; + if (self == nil) return nil; + + self.loggerMock = (ADJLoggerMock *) [ADJAdjustFactory logger]; + + [self.loggerMock test:[launchPrefix stringByAppendingFormat:@"init"]]; + + return self; +} + +- (BOOL)adjustDeeplinkResponse:(NSURL *)deeplink { + [self.loggerMock test:[launchPrefix stringByAppendingFormat:@"adjustDeeplinkResponse launch, %@", deeplink]]; + return YES; +} + +@end + +@interface ADJDeeplinkNotLaunchDelegate() +@property (nonatomic, strong) ADJLoggerMock *loggerMock; +@end + +@implementation ADJDeeplinkNotLaunchDelegate + +- (id) init { + self = [super init]; + if (self == nil) return nil; + + self.loggerMock = (ADJLoggerMock *) [ADJAdjustFactory logger]; + + [self.loggerMock test:[notLaunchPrefix stringByAppendingFormat:@"init"]]; + + return self; +} + +- (BOOL)adjustDeeplinkResponse:(NSURL *)deeplink { + [self.loggerMock test:[notLaunchPrefix stringByAppendingFormat:@"adjustDeeplinkResponse not launch, %@", deeplink]]; + return NO; +} + +@end \ No newline at end of file diff --git a/AdjustTests/ADJLoggerMock.h b/AdjustTests/ADJLoggerMock.h index d8a3c9d6a..93221b37b 100644 --- a/AdjustTests/ADJLoggerMock.h +++ b/AdjustTests/ADJLoggerMock.h @@ -16,5 +16,5 @@ static const int ADJLogLevelCheck = 8; - (void)test:(NSString *)message, ...; - (BOOL)deleteUntil:(NSInteger)logLevel beginsWith:(NSString *)beginsWith; - (void)reset; - +- (NSString *)containsMessage:(NSInteger)logLevel beginsWith:(NSString *)beginsWith; @end diff --git a/AdjustTests/ADJLoggerMock.m b/AdjustTests/ADJLoggerMock.m index 28aac436d..13de15588 100644 --- a/AdjustTests/ADJLoggerMock.m +++ b/AdjustTests/ADJLoggerMock.m @@ -49,6 +49,10 @@ - (NSString *)description { } - (BOOL)deleteUntil:(NSInteger)logLevel beginsWith:(NSString *)beginsWith { + return [self containsMessage:logLevel beginsWith:beginsWith] != nil; +} + +- (NSString *)containsMessage:(NSInteger)logLevel beginsWith:(NSString *)beginsWith { NSMutableArray *logArray = (NSMutableArray *)self.logMap[@(logLevel)]; for (int i = 0; i < [logArray count]; i++) { NSString *logMessage = logArray[i]; @@ -56,12 +60,12 @@ - (BOOL)deleteUntil:(NSInteger)logLevel beginsWith:(NSString *)beginsWith { [logArray removeObjectsInRange:NSMakeRange(0, i + 1)]; [self check:@"found %@", beginsWith]; //NSLog(@"%@ found", beginsWith); - return YES; + return logMessage; } } [self check:@"%@ is not in: %@", beginsWith, [logArray componentsJoinedByString:@","]]; //NSLog(@"%@ not in (%@)", beginsWith, [logArray componentsJoinedByString:@","]); - return NO; + return nil; } - (void)setLogLevel:(ADJLogLevel)logLevel { diff --git a/AdjustTests/ADJPackageFields.h b/AdjustTests/ADJPackageFields.h index 918f68d93..11e35ddf1 100644 --- a/AdjustTests/ADJPackageFields.h +++ b/AdjustTests/ADJPackageFields.h @@ -18,12 +18,14 @@ @property (nonatomic, copy) NSString* purchaseTime; @property (nonatomic, copy) NSString* iadTime; @property (nonatomic, copy) NSString* iadDetails; +@property (nonatomic, copy) NSString* deepLink; // ADJConfig @property (nonatomic, copy) NSString *appToken; @property (nonatomic, copy) NSString *environment; @property (nonatomic, copy) NSString *sdkPrefix; -@property (nonatomic, copy) NSString *hasDelegate; +@property (nonatomic, assign) BOOL hasResponseDelegate; @property (nonatomic, copy) NSString *defaultTracker; +@property (nonatomic, assign) BOOL eventBufferingEnabled; // ADJDeviceInfo @property (nonatomic, copy) NSString *clientSdk; @property (nonatomic, copy) NSString *pushToken; diff --git a/AdjustTests/ADJPackageFields.m b/AdjustTests/ADJPackageFields.m index 95aeca90d..806735f87 100644 --- a/AdjustTests/ADJPackageFields.m +++ b/AdjustTests/ADJPackageFields.m @@ -15,8 +15,8 @@ - (id) init { if (self == nil) return nil; // default values - self.appToken = @"123456789012"; - self.clientSdk = @"ios4.6.0"; + self.appToken = @"qwerty123456"; + self.clientSdk = @"ios4.7.0"; self.suffix = @""; self.environment = @"sandbox"; diff --git a/AdjustTests/ADJPackageHandlerMock.m b/AdjustTests/ADJPackageHandlerMock.m index d7aa9737e..fd01f7b18 100644 --- a/AdjustTests/ADJPackageHandlerMock.m +++ b/AdjustTests/ADJPackageHandlerMock.m @@ -17,7 +17,7 @@ @interface ADJPackageHandlerMock() @property (nonatomic, strong) ADJLoggerMock *loggerMock; @property (nonatomic, assign) id activityHandler; -@property (nonatomic, assign) BOOL startPaused; +@property (nonatomic, assign) BOOL startsSending; @end @@ -26,28 +26,28 @@ @implementation ADJPackageHandlerMock - (id)init { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-designated-initializers" - return [self initWithActivityHandler:nil startPaused:NO]; + return [self initWithActivityHandler:nil startsSending:YES]; #pragma clang diagnostic pop } - (id)initWithActivityHandler:(id)activityHandler - startPaused:(BOOL)startPaused + startsSending:(BOOL)startsSending { self = [super init]; if (self == nil) return nil; - self.startPaused = startPaused; + self.startsSending = startsSending; self.activityHandler = activityHandler; self.loggerMock = (ADJLoggerMock *) ADJAdjustFactory.logger; self.packageQueue = [NSMutableArray array]; - [self.loggerMock test:[prefix stringByAppendingFormat:@"initWithActivityHandler, paused: %d", startPaused]]; + [self.loggerMock test:[prefix stringByAppendingFormat:@"initWithActivityHandler, startsSending: %d", startsSending]]; return self; } - (void)addPackage:(ADJActivityPackage *)package { - [self.loggerMock test:[prefix stringByAppendingString:@"addPackage"]]; + [self.loggerMock test:[prefix stringByAppendingFormat:@"addPackage %d", package.activityKind]]; [self.packageQueue addObject:package]; } @@ -59,7 +59,9 @@ - (void)sendNextPackage:(ADJResponseData *)responseData { [self.loggerMock test:[prefix stringByAppendingString:@"sendNextPackage"]]; } -- (void)closeFirstPackage:(ADJResponseData *)responseData { +- (void)closeFirstPackage:(ADJResponseData *)responseData + activityPackage:(ADJActivityPackage *)activityPackage +{ [self.loggerMock test:[prefix stringByAppendingString:@"closeFirstPackage"]]; } diff --git a/AdjustTests/ADJPackageHandlerTests.m b/AdjustTests/ADJPackageHandlerTests.m index 20687720c..abd64527b 100644 --- a/AdjustTests/ADJPackageHandlerTests.m +++ b/AdjustTests/ADJPackageHandlerTests.m @@ -13,6 +13,9 @@ #import "ADJRequestHandlerMock.h" #import "ADJTestsUtil.h" #import "ADJTestActivityPackage.h" +#import "ADJResponseData.h" +#import "ADJBackoffStrategy.h" +#import "ADJPackageHandler.h" typedef enum { ADJSendFirstEmptyQueue = 0, @@ -87,19 +90,22 @@ - (void)testAddPackage [secondPackageHandler sendFirstPackage]; [NSThread sleepForTimeInterval:1.0]; - aTest(@"RequestHandler sendPackage, clickFirstPackage"); + aTest(@"RequestHandler sendPackage, activityPackage clickFirstPackage"); + aTest(@"RequestHandler sendPackage, queueSize 2"); - // send the second click package/ third package + // send the second package [secondPackageHandler sendNextPackage:nil]; [NSThread sleepForTimeInterval:1.0]; - aTest(@"RequestHandler sendPackage, clickThirdPackage"); + aTest(@"RequestHandler sendPackage, activityPackage unknownSecondPackage"); + aTest(@"RequestHandler sendPackage, queueSize 1"); // send the unknow package/ second package [secondPackageHandler sendNextPackage:nil]; [NSThread sleepForTimeInterval:1.0]; - aTest(@"RequestHandler sendPackage, unknownSecondPackage"); + aTest(@"RequestHandler sendPackage, activityPackage clickThirdPackage"); + aTest(@"RequestHandler sendPackage, queueSize 0"); } - (void)testSendFirst @@ -136,13 +142,13 @@ - (void)testSendFirst // verify that both paused and isSending are reset with a new session id secondpackageHandler = [ADJAdjustFactory packageHandlerForActivityHandler:self.activityHandlerMock - startPaused:NO]; + startsSending:YES]; [secondpackageHandler sendFirstPackage]; [NSThread sleepForTimeInterval:1.0]; // send the package to request handler - [self checkSendFirst:ADJSendFirstSend packageString:@"unknownFirstPackage"]; + [self checkSendFirst:ADJSendFirstSend queueSize:0 packageString:@"unknownFirstPackage"]; } - (void)testSendNext @@ -172,7 +178,7 @@ - (void)testSendNext aDebug(@"Package handler wrote 1 packages"); // try to send the second package - [self checkSendFirst:ADJSendFirstSend packageString:@"unknownSecondPackage"]; + [self checkSendFirst:ADJSendFirstSend queueSize:0 packageString:@"unknownSecondPackage"]; } - (void)testCloseFirstPackage @@ -180,6 +186,8 @@ - (void)testCloseFirstPackage // reseting to make the test order independent [self reset]; + [ADJAdjustFactory setPackageHandlerBackoffStrategy:[ADJBackoffStrategy backoffStrategyWithType:ADJNoWait]]; + // initialize Package Handler id packageHandler = [self createFirstPackageHandler]; @@ -192,28 +200,200 @@ - (void)testCloseFirstPackage [self checkSendFirst:ADJSendFirstIsSending]; //send next package - [packageHandler closeFirstPackage:nil]; + ADJActivityPackage *activityPackage = [[ADJActivityPackage alloc] init]; + ADJResponseData * responseData = [ADJResponseData buildResponseData:activityPackage]; + [packageHandler closeFirstPackage:responseData activityPackage:activityPackage]; [NSThread sleepForTimeInterval:2.0]; + aTest(@"ActivityHandler finishedTracking, message:(null) timestamp:(null) adid:(null)"); + aVerbose(@"Package handler can send"); + anDebug(@"Package handler wrote"); - [packageHandler sendFirstPackage]; - [NSThread sleepForTimeInterval:2.0]; + // tries to send the next package after sleeping + [self checkSendFirst:ADJSendFirstSend queueSize:0 packageString:@"unknownFirstPackage"]; +} + +- (void) testBackoffJitter +{ + // reseting to make the test order independent + [self reset]; + + [ADJAdjustFactory setPackageHandlerBackoffStrategy:[ADJBackoffStrategy backoffStrategyWithType:ADJTestWait]]; + + id packageHandler = [self createFirstPackageHandler]; + + ADJActivityPackage * activityPackage = [ADJTestsUtil getUnknowPackage:@"FirstPackage"]; + + ADJResponseData * responseData = [ADJResponseData buildResponseData:activityPackage]; + //Pattern pattern = Pattern.compile("Sleeping for (\\d+\\.\\d) seconds before retrying the (\\d+) time"); + + NSString * sleepingLogPattern = @"Sleeping for (\\d+\\.\\d) seconds before retrying the (\\d+) time"; + NSError *error = NULL; + NSRegularExpression *regex = [NSRegularExpression + regularExpressionWithPattern:sleepingLogPattern + options:NSRegularExpressionCaseInsensitive + error:&error]; + + if (error != nil) { + [self.loggerMock test:@"regex error %@", error.description]; + aFail(); + } + + // 1st + [packageHandler closeFirstPackage:responseData activityPackage:activityPackage]; + [NSThread sleepForTimeInterval:1.5]; + + NSString * sleepingLogMessage = [self.loggerMock containsMessage:ADJLogLevelVerbose beginsWith:@"Sleeping for"]; + anNil(sleepingLogMessage); + // Sleeping for 0.1 seconds before retrying the 1 time + + [self checkSleeping:regex + sleepingLog:sleepingLogMessage + minRange:0.1 + maxRange:0.2 + maxCeiling:1 + minCeiling:0.5 + numberRetries:1]; + + // 2nd + [packageHandler closeFirstPackage:responseData activityPackage:activityPackage]; + [NSThread sleepForTimeInterval:1.5]; + + sleepingLogMessage = [self.loggerMock containsMessage:ADJLogLevelVerbose beginsWith:@"Sleeping for"]; + anNil(sleepingLogMessage); + + [self checkSleeping:regex + sleepingLog:sleepingLogMessage + minRange:0.2 + maxRange:0.4 + maxCeiling:1 + minCeiling:0.5 + numberRetries:2]; + + // 3rd + [packageHandler closeFirstPackage:responseData activityPackage:activityPackage]; + [NSThread sleepForTimeInterval:1.5]; + + sleepingLogMessage = [self.loggerMock containsMessage:ADJLogLevelVerbose beginsWith:@"Sleeping for"]; + anNil(sleepingLogMessage); + + [self checkSleeping:regex + sleepingLog:sleepingLogMessage + minRange:0.4 + maxRange:0.8 + maxCeiling:1 + minCeiling:0.5 + numberRetries:3]; + + // 4th + [packageHandler closeFirstPackage:responseData activityPackage:activityPackage]; + [NSThread sleepForTimeInterval:1.5]; + + sleepingLogMessage = [self.loggerMock containsMessage:ADJLogLevelVerbose beginsWith:@"Sleeping for"]; + anNil(sleepingLogMessage); + + [self checkSleeping:regex + sleepingLog:sleepingLogMessage + minRange:0.8 + maxRange:1.6 + maxCeiling:1 + minCeiling:0.5 + numberRetries:4]; + + // 5th + [packageHandler closeFirstPackage:responseData activityPackage:activityPackage]; + [NSThread sleepForTimeInterval:1.5]; + + sleepingLogMessage = [self.loggerMock containsMessage:ADJLogLevelVerbose beginsWith:@"Sleeping for"]; + anNil(sleepingLogMessage); + + [self checkSleeping:regex + sleepingLog:sleepingLogMessage + minRange:1.6 + maxRange:3.2 + maxCeiling:1 + minCeiling:0.5 + numberRetries:5]; + + // 6th + [packageHandler closeFirstPackage:responseData activityPackage:activityPackage]; + [NSThread sleepForTimeInterval:1.5]; + + sleepingLogMessage = [self.loggerMock containsMessage:ADJLogLevelVerbose beginsWith:@"Sleeping for"]; + anNil(sleepingLogMessage); + + [self checkSleeping:regex + sleepingLog:sleepingLogMessage + minRange:6.4 + maxRange:12.8 + maxCeiling:1 + minCeiling:0.5 + numberRetries:6]; +} + +- (void)checkSleeping:(NSRegularExpression *)regex + sleepingLog:(NSString *)sleepingLog + minRange:(double)minRange + maxRange:(double)maxRange + maxCeiling:(NSInteger)maxCeiling + minCeiling:(double)minCeiling + numberRetries:(NSInteger)numberRetries +{ + NSArray *matches = [regex matchesInString:sleepingLog options:0 range:NSMakeRange(0, [sleepingLog length])]; + + if ([matches count] == 0) { + aFail(); + } + + NSTextCheckingResult *match = matches[0]; + + if ([match numberOfRanges] != 3) { + aFail(); + } + + NSString * sleepingTimeString = [sleepingLog substringWithRange:[match rangeAtIndex:1]]; + double sleepingTime = [sleepingTimeString doubleValue]; + + [self.loggerMock test:@"sleeping time %f", sleepingTime]; + + BOOL failsCeiling = sleepingTime > maxCeiling; + aFalse(failsCeiling); + + if (maxRange < maxCeiling) { + BOOL failsMinRange = sleepingTime < minRange; + aFalse(failsMinRange); + } else { + BOOL failsMinRange = sleepingTime < minCeiling ; + aFalse(failsMinRange); + } + + if (maxRange < maxCeiling) { + BOOL failsMaxRange = sleepingTime > maxRange; + aFalse(failsMaxRange); + } else { + BOOL failsMaxRange = sleepingTime > maxCeiling; + aFalse(failsMaxRange); + } - // try to send the first package again - [self checkSendFirst:ADJSendFirstSend packageString:@"unknownFirstPackage"]; + NSString * retryTimeString = [sleepingLog substringWithRange:[match rangeAtIndex:2]]; + NSInteger retryTime = [retryTimeString integerValue]; + + [self.loggerMock test:@"retry time %ld", retryTime]; + + aliEquals(numberRetries, retryTime); } - (id)createFirstPackageHandler { - return [self createFirstPackageHandler:NO]; + return [self createFirstPackageHandler:YES]; } -- (id)createFirstPackageHandler:(BOOL)startPaused +- (id)createFirstPackageHandler:(BOOL)startsSending { // initialize Package Handler - id packageHandler = [ADJAdjustFactory packageHandlerForActivityHandler:self.activityHandlerMock - startPaused:startPaused]; + id packageHandler = [ADJPackageHandler handlerWithActivityHandler:self.activityHandlerMock startsSending:startsSending]; + [NSThread sleepForTimeInterval:2.0]; aVerbose(@"Package queue file not found"); @@ -224,7 +404,7 @@ - (void)testCloseFirstPackage - (id)checkAddSecondPackage { id packageHandler = [ADJAdjustFactory packageHandlerForActivityHandler:self.activityHandlerMock - startPaused:NO]; + startsSending:YES]; [NSThread sleepForTimeInterval:2.0]; @@ -241,7 +421,7 @@ - (void)testCloseFirstPackage { if (packageHandler == nil) { packageHandler = [ADJAdjustFactory packageHandlerForActivityHandler:self.activityHandlerMock - startPaused:NO]; + startsSending:YES]; [NSThread sleepForTimeInterval:2.0]; @@ -274,14 +454,15 @@ - (void)checkAddAndSendFirst:(id)packageHandler [self checkAddPackage:1 packageString:@"unknownFirstPackage"]; - [self checkSendFirst:ADJSendFirstSend packageString:@"unknownFirstPackage"]; + [self checkSendFirst:ADJSendFirstSend queueSize:0 packageString:@"unknownFirstPackage"]; } - (void)checkSendFirst:(ADJSendFirst)sendFirstState { - [self checkSendFirst:sendFirstState packageString:nil]; + [self checkSendFirst:sendFirstState queueSize:0 packageString:nil]; } - (void)checkSendFirst:(ADJSendFirst)sendFirstState + queueSize:(NSUInteger)queueSize packageString:(NSString*)packageString { if (sendFirstState == ADJSendFirstPaused) { @@ -297,8 +478,10 @@ - (void)checkSendFirst:(ADJSendFirst)sendFirstState } if (sendFirstState == ADJSendFirstSend) { - NSString * aSend = [NSString stringWithFormat:@"RequestHandler sendPackage, %@", packageString]; - aTest(aSend); + NSString * aActivitySend = [NSString stringWithFormat:@"RequestHandler sendPackage, activityPackage %@", packageString]; + aTest(aActivitySend); + NSString * aQueueSizeSend = [NSString stringWithFormat:@"RequestHandler sendPackage, queueSize %lu", queueSize]; + aTest(aQueueSizeSend); } else { anTest(@"RequestHandler sendPackage"); } @@ -313,4 +496,5 @@ - (void)checkAddPackage:(int)packageNumber NSString * aPackagesWrote = [NSString stringWithFormat:@"Package handler wrote %d packages", packageNumber]; aDebug(aPackagesWrote); } + @end diff --git a/AdjustTests/ADJRequestHandlerMock.m b/AdjustTests/ADJRequestHandlerMock.m index 2200a273d..ae4eeeeb2 100644 --- a/AdjustTests/ADJRequestHandlerMock.m +++ b/AdjustTests/ADJRequestHandlerMock.m @@ -35,8 +35,11 @@ - (id)initWithPackageHandler:(id) packageHandler { return self; } -- (void)sendPackage:(ADJActivityPackage *)activityPackage { - [self.loggerMock test:[prefix stringByAppendingFormat:@"sendPackage, %@", activityPackage]]; +- (void)sendPackage:(ADJActivityPackage *)activityPackage + queueSize:(NSUInteger)queueSize +{ + [self.loggerMock test:[prefix stringByAppendingFormat:@"sendPackage, activityPackage %@", activityPackage]]; + [self.loggerMock test:[prefix stringByAppendingFormat:@"sendPackage, queueSize %lu", queueSize]]; /* NSDictionary *jsonDict; diff --git a/AdjustTests/AIRequestHandlerTests.m b/AdjustTests/ADJRequestHandlerTests.m similarity index 90% rename from AdjustTests/AIRequestHandlerTests.m rename to AdjustTests/ADJRequestHandlerTests.m index 263431b97..1573794c6 100644 --- a/AdjustTests/AIRequestHandlerTests.m +++ b/AdjustTests/ADJRequestHandlerTests.m @@ -31,13 +31,14 @@ - (void)setUp [super setUp]; // Put setup code here; it will be run once, before the first test case. - [self reset]; - + //[self reset]; } - (void)tearDown { [ADJAdjustFactory setLogger:nil]; + [ADJAdjustFactory setPackageHandler:nil]; + [NSURLConnection reset]; // Put teardown code here; it will be run once, after the last test case. [super tearDown]; @@ -50,11 +51,15 @@ - (void)reset { self.packageHandlerMock = [[ADJPackageHandlerMock alloc] init]; [ADJAdjustFactory setPackageHandler:self.packageHandlerMock]; - self.requestHandler =[ADJAdjustFactory requestHandlerForPackageHandler:self.packageHandlerMock]; + self.requestHandler = [ADJAdjustFactory requestHandlerForPackageHandler:self.packageHandlerMock]; + [NSURLConnection reset]; } - (void)testSend { + // reseting to make the test order independent + [self reset]; + // null response [NSURLSession setResponseType:ADJSessionResponseTypeNil]; @@ -118,7 +123,7 @@ - (void)testSend - (void)checkSendPackage { - [self.requestHandler sendPackage:[ADJTestsUtil getUnknowPackage:@""]]; + [self.requestHandler sendPackage:[ADJTestsUtil getUnknowPackage:@""] queueSize:0]; [NSThread sleepForTimeInterval:1.0]; diff --git a/AdjustTests/ADJSdkClickHandlerMock.h b/AdjustTests/ADJSdkClickHandlerMock.h new file mode 100644 index 000000000..5cb454868 --- /dev/null +++ b/AdjustTests/ADJSdkClickHandlerMock.h @@ -0,0 +1,16 @@ +// +// ADJSdkClickHandlerMock.h +// Adjust +// +// Created by Pedro Filipe on 02/05/16. +// Copyright © 2016 adjust GmbH. All rights reserved. +// + +#import +#import "ADJSdkClickHandler.h" + +@interface ADJSdkClickHandlerMock : NSObject + +@property (nonatomic, strong) NSMutableArray *packageQueue; + +@end diff --git a/AdjustTests/ADJSdkClickHandlerMock.m b/AdjustTests/ADJSdkClickHandlerMock.m new file mode 100644 index 000000000..6715cf96b --- /dev/null +++ b/AdjustTests/ADJSdkClickHandlerMock.m @@ -0,0 +1,50 @@ +// +// ADJSdkClickHandlerMock.m +// Adjust +// +// Created by Pedro Filipe on 02/05/16. +// Copyright © 2016 adjust GmbH. All rights reserved. +// + +#import "ADJSdkClickHandlerMock.h" +#import "ADJLoggerMock.h" +#import "ADJAdjustFactory.h" +#import "ADJActivityPackage.h" + +static NSString * const prefix = @"SdkClickHandler "; + +@interface ADJSdkClickHandlerMock() + +@property (nonatomic, strong) ADJLoggerMock *loggerMock; + +@end + +@implementation ADJSdkClickHandlerMock + +- (id)initWithStartsSending:(BOOL)startsSending { + self = [super init]; + if (self == nil) return nil; + + self.loggerMock = (ADJLoggerMock *) [ADJAdjustFactory logger]; + + [self.loggerMock test:[prefix stringByAppendingFormat:@"initWithStartsSending, startsSending: %d", startsSending]]; + + self.packageQueue = [NSMutableArray array]; + + return self; +} + +- (void)pauseSending { + [self.loggerMock test:[prefix stringByAppendingFormat:@"pauseSending"]]; +} + +- (void)resumeSending { + [self.loggerMock test:[prefix stringByAppendingFormat:@"resumeSending"]]; +} + +- (void)sendSdkClick:(ADJActivityPackage *)sdkClickPackage { + [self.loggerMock test:[prefix stringByAppendingFormat:@"sendSdkClick"]]; + [self.packageQueue addObject:sdkClickPackage]; +} + +@end diff --git a/AdjustTests/ADJSdkClickHandlerTests.m b/AdjustTests/ADJSdkClickHandlerTests.m new file mode 100644 index 000000000..9a7a594dd --- /dev/null +++ b/AdjustTests/ADJSdkClickHandlerTests.m @@ -0,0 +1,307 @@ +// +// ADJClickHandlerTests.m +// Adjust +// +// Created by Pedro Filipe on 19/05/16. +// Copyright © 2016 adjust GmbH. All rights reserved. +// + +#import +#import +#import "ADJTestActivityPackage.h" +#import "ADJAdjustFactory.h" +#import "ADJLoggerMock.h" +#import "NSURLConnection+NSURLConnectionSynchronousLoadingMocking.h" +#import "NSURLSession+NSURLDataWithRequestMocking.h" +#import "ADJActivityHandlerMock.h" +#import "ADJSdkClickHandlerMock.h" +#import "ADJAttributionHandlerMock.h" +#import "ADJPackageHandlerMock.h" +#import "ADJBackoffStrategy.h" +#import "ADJSdkClickHandler.h" + +@interface ADJSdkClickHandlerTests : ADJTestActivityPackage + +@property (atomic,strong) ADJActivityHandlerMock *activityHandlerMock; +@property (atomic,strong) ADJActivityPackage * sdkClickPackage; + +@end + +@implementation ADJSdkClickHandlerTests + +- (void)setUp +{ + [super setUp]; + // Put setup code here; it will be run once, before the first test case. +} + +- (void)tearDown +{ + [ADJAdjustFactory setLogger:nil]; + + // Put teardown code here; it will be run once, after the last test case. + [ADJAdjustFactory setPackageHandler:nil]; + [ADJAdjustFactory setAttributionHandler:nil]; + [NSURLSession reset]; + + [super tearDown]; +} + +- (void)reset { + self.loggerMock = [[ADJLoggerMock alloc] init]; + [ADJAdjustFactory setLogger:self.loggerMock]; + + ADJConfig * config = [ADJConfig configWithAppToken:@"qwerty123456" environment:ADJEnvironmentSandbox]; + + self.activityHandlerMock = [[ADJActivityHandlerMock alloc] initWithConfig:config]; + self.sdkClickPackage = [self getClickPackage]; + + [NSURLSession reset]; +} + +- (void) testPaused { + // reseting to make the test order independent + [self reset]; + + self.sdkClickPackage.clientSdk = @"Test-First-Click"; + + ADJActivityPackage * secondSdkClickPackage = [self getClickPackage]; + secondSdkClickPackage.clientSdk = @"Test-Second-Click"; + + [ADJAdjustFactory setSdkClickHandlerBackoffStrategy:[ADJBackoffStrategy backoffStrategyWithType:ADJNoWait]]; + + id sdkClickHandler = [ADJSdkClickHandler handlerWithStartsSending:NO]; + + [NSURLSession setResponseType:ADJSessionResponseTypeConnError]; + + [sdkClickHandler sendSdkClick:self.sdkClickPackage]; + [NSThread sleepForTimeInterval:1.0]; + + // added first click package to the queue + aDebug(@"Added sdk_click 1"); + aVerbose(@"Path: /sdk_click\nClientSdk: Test-First-Click"); + + // but not send because it's paused + anTest(@"NSURLSession dataTaskWithRequest"); + + // send second sdk click + [sdkClickHandler sendSdkClick:secondSdkClickPackage]; + [NSThread sleepForTimeInterval:1.0]; + + // added second click package to the queue + aDebug(@"Added sdk_click 2"); + + aVerbose(@"Path: /sdk_click\nClientSdk: Test-Second-Click"); + + // wait two seconds before sending + [NSURLSession setWaitingTime:2]; + + // try to send first package + [self checkSendPackage:sdkClickHandler retries:1 clientSdk:@"Test-First-Click"]; + // and then the second + [self checkSendPackage:sdkClickHandler retries:1 clientSdk:@"Test-Second-Click"]; + + // try to send first package again + [self checkSendPackage:sdkClickHandler retries:2 clientSdk:@"Test-First-Click"]; + // and then the second again + [self checkSendPackage:sdkClickHandler retries:2 clientSdk:@"Test-Second-Click"]; +} + +- (void)testNullResponse { + // reseting to make the test order independent + [self reset]; + + [ADJAdjustFactory setSdkClickHandlerBackoffStrategy:[ADJBackoffStrategy backoffStrategyWithType:ADJNoRetry]]; + + id sdkClickHandler = [ADJSdkClickHandler handlerWithStartsSending:YES]; + + [NSURLSession setResponseType:ADJSessionResponseTypeNil]; + + [sdkClickHandler sendSdkClick:self.sdkClickPackage]; + [NSThread sleepForTimeInterval:1.0]; + + aDebug(@"Added sdk_click 1"); + + //assertUtil.test("MockHttpsURLConnection getInputStream, responseType: null"); + aTest(@"NSURLSession dataTaskWithRequest"); + + aError(@"Failed to track click (empty error) Will retry later"); + + // tries to retry + aError(@"Retrying sdk_click package for the 1 time"); + + // adds to end of the queue + aDebug(@"Added sdk_click 1"); + + // waiting to try again + aVerbose(@"Sleeping for"); +} + +- (void)testClientException { + // reseting to make the test order independent + [self reset]; + + [ADJAdjustFactory setSdkClickHandlerBackoffStrategy:[ADJBackoffStrategy backoffStrategyWithType:ADJNoRetry]]; + + id sdkClickHandler = [ADJSdkClickHandler handlerWithStartsSending:YES]; + + [NSURLSession setResponseType:ADJSessionResponseTypeConnError]; + + [sdkClickHandler sendSdkClick:self.sdkClickPackage]; + [NSThread sleepForTimeInterval:1.0]; + + //assertUtil.test("MockHttpsURLConnection getInputStream, responseType: CLIENT_PROTOCOL_EXCEPTION"); + aTest(@"NSURLSession dataTaskWithRequest"); + + aError(@"Failed to track click (connection error) Will retry later"); + + // tries to retry + aError(@"Retrying sdk_click package for the 1 time"); + + // adds to end of the queue + aDebug(@"Added sdk_click 1"); + + // waiting to try again + aVerbose(@"Sleeping for"); +} + +- (void)testServerError { + // reseting to make the test order independent + [self reset]; + + [ADJAdjustFactory setSdkClickHandlerBackoffStrategy:[ADJBackoffStrategy backoffStrategyWithType:ADJNoWait]]; + + id sdkClickHandler = [ADJSdkClickHandler handlerWithStartsSending:YES]; + + [NSURLSession setResponseType:ADJSessionResponseTypeServerError]; + + [sdkClickHandler sendSdkClick:self.sdkClickPackage]; + [NSThread sleepForTimeInterval:1.0]; + + aTest(@"NSURLSession dataTaskWithRequest"); + + aVerbose(@"Response: { \"message\": \"testResponseError\"}"); + + aError(@"testResponseError"); +} + +- (void)testWrongJson { + // reseting to make the test order independent + [self reset]; + + [ADJAdjustFactory setSdkClickHandlerBackoffStrategy:[ADJBackoffStrategy backoffStrategyWithType:ADJNoRetry]]; + + id sdkClickHandler = [ADJSdkClickHandler handlerWithStartsSending:YES]; + + [NSURLSession setResponseType:ADJSessionResponseTypeWrongJson]; + + [sdkClickHandler sendSdkClick:self.sdkClickPackage]; + [NSThread sleepForTimeInterval:1.0]; + + aTest(@"NSURLSession dataTaskWithRequest"); + + aVerbose(@"Response: not a json response"); + + aError(@"Failed to parse json response. (The data couldn’t be read because it isn’t in the correct format.)"); +} + +- (void)testEmptyJson { + // reseting to make the test order independent + [self reset]; + + [ADJAdjustFactory setSdkClickHandlerBackoffStrategy:[ADJBackoffStrategy backoffStrategyWithType:ADJNoRetry]]; + + id sdkClickHandler = [ADJSdkClickHandler handlerWithStartsSending:YES]; + + [NSURLSession setResponseType:ADJSessionResponseTypeEmptyJson]; + + [sdkClickHandler sendSdkClick:self.sdkClickPackage]; + [NSThread sleepForTimeInterval:1.0]; + + aTest(@"NSURLSession dataTaskWithRequest"); + + aVerbose(@"Response: { }"); + + aInfo(@"No message found"); +} + +- (void)testMessage { + // reseting to make the test order independent + [self reset]; + + [ADJAdjustFactory setSdkClickHandlerBackoffStrategy:[ADJBackoffStrategy backoffStrategyWithType:ADJNoRetry]]; + + id sdkClickHandler = [ADJSdkClickHandler handlerWithStartsSending:YES]; + + [NSURLSession setResponseType:ADJSessionResponseTypeMessage]; + + [sdkClickHandler sendSdkClick:self.sdkClickPackage]; + [NSThread sleepForTimeInterval:1.0]; + + aTest(@"NSURLSession dataTaskWithRequest"); + + aVerbose(@"Response: { \"message\" : \"response OK\"}"); + + aInfo(@"response OK"); +} +- (void)checkSendPackage:(id)sdkClickHandler + retries:(NSInteger)retries + clientSdk:(NSString *)clientSdk +{ + // try to send the second package that is at the start of the queue + [sdkClickHandler resumeSending]; + + [NSThread sleepForTimeInterval:1.0]; + + // prevent sending next again + [sdkClickHandler pauseSending]; + + [NSThread sleepForTimeInterval:2.0]; + + // check that it tried to send the second package + //assertUtil.test("MockHttpsURLConnection setRequestProperty, field Client-SDK, newValue Test-Second-Click"); + + // and that it will try to send it again + NSString * errorLog = [NSString stringWithFormat:@"Retrying sdk_click package for the %ld time", retries]; + aError(errorLog); + + // second package added again on the end of the queue + aDebug(@"Added sdk_click 2"); + + NSString * messageLog = [NSString stringWithFormat:@"Path: /sdk_click\nClientSdk: %@", clientSdk]; + aVerbose(messageLog); + + // does not continue to send because it was paused + //assertUtil.notInTest("MockHttpsURLConnection setRequestProperty"); +} + + + + +- (ADJActivityPackage *) getClickPackage { + ADJSdkClickHandlerMock * mockSdkClickHandler = [ADJSdkClickHandlerMock alloc]; + ADJAttributionHandlerMock * mockAttributionHandler = [ADJAttributionHandlerMock alloc]; + + ADJPackageHandlerMock * mockPackageHandler = [ADJPackageHandlerMock alloc]; + + [ADJAdjustFactory setPackageHandler:mockPackageHandler]; + [ADJAdjustFactory setSdkClickHandler:mockSdkClickHandler]; + [ADJAdjustFactory setAttributionHandler:mockAttributionHandler]; + + // create the config to start the session + ADJConfig * config = [ADJConfig configWithAppToken:@"qwerty123456" environment:@"sandbox"]; + + // start activity handler with config + id activityHandler = [ADJActivityHandler handlerWithConfig:config]; + [activityHandler applicationDidBecomeActive]; + [activityHandler appWillOpenUrl:[NSURL URLWithString:@"AdjustTests://"]]; + [NSThread sleepForTimeInterval:2.0]; + + ADJActivityPackage * sdkClickPackage = (ADJActivityPackage *) mockSdkClickHandler.packageQueue[0]; + [self.loggerMock reset]; + + return sdkClickPackage; +} + + +@end diff --git a/AdjustTests/ADJSessionState.h b/AdjustTests/ADJSessionState.h new file mode 100644 index 000000000..0d14bd1e7 --- /dev/null +++ b/AdjustTests/ADJSessionState.h @@ -0,0 +1,47 @@ +// +// ADJSessionState.h +// Adjust +// +// Created by Pedro Filipe on 10/05/16. +// Copyright © 2016 adjust GmbH. All rights reserved. +// + +#import + +typedef enum { + ADJSessionTypeSession = 1, + ADJSessionTypeSubSession = 2, + ADJSessionTypeTimeTravel = 3, + ADJSessionTypeNonSession = 4 +} ADJSessionType; + +@interface ADJSessionState : NSObject + +@property (nonatomic, assign) BOOL toSend; +@property (nonatomic, assign) BOOL paused; +@property (nonatomic, assign) NSInteger sessionCount; +@property (nonatomic, assign) NSInteger subsessionCount; +@property (nonatomic, assign) ADJSessionType sessionType; +@property (nonatomic, assign) NSInteger eventCount; +@property (nonatomic, copy) NSNumber * getAttributionIsCalled; +@property (nonatomic, assign) BOOL timerAlreadyStarted; +@property (nonatomic, assign) BOOL eventBufferingIsEnabled; +@property (nonatomic, assign) BOOL foregroundTimerStarts; +@property (nonatomic, assign) BOOL foregroundTimerAlreadyStarted; +/* +boolean toSend = true; +boolean paused = false; +int sessionCount = 1; +int subsessionCount = 1; +SessionType sessionType = null; +int eventCount = 0; +Boolean getAttributionIsCalled = null; +Boolean timerAlreadyStarted = false; +boolean eventBufferingIsEnabled = false; +boolean foregroundTimerStarts = true; +boolean foregroundTimerAlreadyStarted = false; +*/ + +- (id)initWithSessionType:(ADJSessionType)sessionType; ++ (ADJSessionState *)sessionStateWithSessionType:(ADJSessionType)sessionType; +@end diff --git a/AdjustTests/ADJSessionState.m b/AdjustTests/ADJSessionState.m new file mode 100644 index 000000000..15cab592b --- /dev/null +++ b/AdjustTests/ADJSessionState.m @@ -0,0 +1,43 @@ +// +// ADJSessionState.m +// Adjust +// +// Created by Pedro Filipe on 10/05/16. +// Copyright © 2016 adjust GmbH. All rights reserved. +// + +#import "ADJSessionState.h" + +@implementation ADJSessionState + +- (id)initWithSessionType:(ADJSessionType)sessionType { + self = [super init]; + if (self == nil) return nil; + + // default values + self.toSend = YES; + self.paused = NO; + self.sessionCount = 1; + self.subsessionCount = 1; + self.eventCount = 0; + self.getAttributionIsCalled = nil; + self.timerAlreadyStarted = NO; + self.eventBufferingIsEnabled = NO; + self.foregroundTimerStarts = YES; + self.foregroundTimerAlreadyStarted = NO; + + if (sessionType == ADJSessionTypeSubSession || + sessionType == ADJSessionTypeNonSession) + { + self.timerAlreadyStarted = YES; + } + self.sessionType = sessionType; + + return self; +} + ++ (ADJSessionState *)sessionStateWithSessionType:(ADJSessionType)sessionType { + return [[ADJSessionState alloc] initWithSessionType:sessionType]; +} + +@end diff --git a/AdjustTests/ADJTest.h b/AdjustTests/ADJTest.h index 30fff214e..17de36030 100644 --- a/AdjustTests/ADJTest.h +++ b/AdjustTests/ADJTest.h @@ -90,6 +90,10 @@ #define ailEquals(field, value, log) \ XCTAssertEqual(field, value, @"f:%d, v:%d, l:%@", field, value, log) +// assert equals long integer log +#define alilEquals(field, value, log) \ + XCTAssertEqual(field, value, @"f:%ld, v:%ld, l:%@", field, value, log) + // assert equals log #define alEquals(field, value, log) \ XCTAssertEqual(field, value, @"f:%@, v:%@, l:%@", field, value, log) @@ -106,6 +110,10 @@ #define aiEquals(field, value) \ ailEquals(field, value, self.loggerMock) +// assert equals long integer +#define aliEquals(field, value) \ + alilEquals(field, value, self.loggerMock) + // assert equals #define aEquals(field, value) \ alEquals(field, value, self.loggerMock) diff --git a/AdjustTests/ADJTestActivityPackage.h b/AdjustTests/ADJTestActivityPackage.h index 7cc3b403e..54ca49333 100644 --- a/AdjustTests/ADJTestActivityPackage.h +++ b/AdjustTests/ADJTestActivityPackage.h @@ -17,7 +17,7 @@ fields:(ADJPackageFields *)fields sessionCount:(NSString*)sessionCount; -- (void)testEventSession:(ADJActivityPackage *)package +- (void)testEventPackage:(ADJActivityPackage *)package fields:(ADJPackageFields *)fields eventToken:(NSString*)eventToken; diff --git a/AdjustTests/ADJTestActivityPackage.m b/AdjustTests/ADJTestActivityPackage.m index 7445a96df..00b3ff49b 100644 --- a/AdjustTests/ADJTestActivityPackage.m +++ b/AdjustTests/ADJTestActivityPackage.m @@ -26,9 +26,6 @@ #define apspEquals(parameterName, value) \ apsEquals((NSString *)package.parameters[parameterName], value) -// assert package integer parameter equals -#define apipEquals(parameterName, value) \ - apspEquals(parameterName, [NSString stringWithFormat:@"%d",value]) // assert package parameter not nil #define appnNil(parameterName) \ @@ -87,7 +84,7 @@ - (void)testPackageSession:(ADJActivityPackage *)package apspEquals(@"default_tracker", fields.defaultTracker); } -- (void)testEventSession:(ADJActivityPackage *)package +- (void)testEventPackage:(ADJActivityPackage *)package fields:(ADJPackageFields *)fields eventToken:(NSString*)eventToken { @@ -157,6 +154,7 @@ - (void)testClickPackage:(ADJActivityPackage *)package // TODO test click time if ([source isEqualToString:@"deeplink"]) { appnNil(@"click_time"); + apspEquals(@"deeplink", fields.deepLink); } else { apspEquals(@"click_time", fields.iadTime); } @@ -187,6 +185,9 @@ - (void)testClickPackage:(ADJActivityPackage *)package // details apspEquals(@"details", fields.iadDetails); + + // push_token + apspEquals(@"push_token", fields.pushToken); } - (void)testAttributionPackage:(ADJActivityPackage *)package @@ -258,8 +259,6 @@ - (void)testDeviceInfo:(ADJActivityPackage *)package //appnNil(@"fb_id"); // tracking_enabled appnNil(@"tracking_enabled"); - // push_token - apspEquals(@"push_token", fields.pushToken); // bundle_id //appnNil(@"bundle_id"); // app_version @@ -276,6 +275,10 @@ - (void)testDeviceInfo:(ADJActivityPackage *)package appnNil(@"language"); // country appnNil(@"country"); + // hardware_name + appnNil(@"hardware_name"); + // cpu_type + appnNil(@"cpu_type"); } - (void)testConfig:(ADJActivityPackage *)package @@ -286,14 +289,13 @@ - (void)testConfig:(ADJActivityPackage *)package // environment apspEquals(@"environment", fields.environment); // needs_attribution_data - if (fields.hasDelegate == nil) { - appnNil(@"needs_response_details"); - } else { - apspEquals(@"needs_response_details", fields.hasDelegate); - } + NSString * hasDelegateString = [NSString stringWithFormat:@"%d",fields.hasResponseDelegate]; + apspEquals(@"needs_response_details", hasDelegateString); + // event_buffering_enabled + NSString * eventBufferingEnabledString = [NSString stringWithFormat:@"%d",fields.eventBufferingEnabled]; + apspEquals(@"event_buffering_enabled", eventBufferingEnabledString); } - - (void)testActivityState:(ADJActivityPackage *)package fields:(ADJPackageFields *)fields { diff --git a/AdjustTests/ADJTrackingSucceededDelegate.h b/AdjustTests/ADJTrackingDelegate.h similarity index 56% rename from AdjustTests/ADJTrackingSucceededDelegate.h rename to AdjustTests/ADJTrackingDelegate.h index 0fd1d3098..496e8aea8 100644 --- a/AdjustTests/ADJTrackingSucceededDelegate.h +++ b/AdjustTests/ADJTrackingDelegate.h @@ -1,8 +1,8 @@ // -// ADJTrackingSucceededDelegate.h -// adjust +// ADJTrackingDelegate.h +// Adjust // -// Created by Pedro Filipe on 22/01/16. +// Created by Pedro Filipe on 13/05/16. // Copyright © 2016 adjust GmbH. All rights reserved. // @@ -12,3 +12,7 @@ @interface ADJTrackingSucceededDelegate : NSObject @end + +@interface ADJTrackingFailedDelegate : NSObject + +@end diff --git a/AdjustTests/ADJTrackingDelegate.m b/AdjustTests/ADJTrackingDelegate.m new file mode 100644 index 000000000..f646a353b --- /dev/null +++ b/AdjustTests/ADJTrackingDelegate.m @@ -0,0 +1,67 @@ +// +// ADJTrackingDelegate.m +// Adjust +// +// Created by Pedro Filipe on 13/05/16. +// Copyright © 2016 adjust GmbH. All rights reserved. +// + +#import "ADJTrackingDelegate.h" +#import "ADJLoggerMock.h" +#import "ADJAdjustFactory.h" + +static NSString * const succeededPrefix = @"ADJTrackingSucceededDelegate "; +static NSString * const failedPrefix = @"ADJTrackingFailedDelegate "; + +@interface ADJTrackingSucceededDelegate() +@property (nonatomic, strong) ADJLoggerMock *loggerMock; +@end + +@implementation ADJTrackingSucceededDelegate + +- (id) init { + self = [super init]; + if (self == nil) return nil; + + self.loggerMock = (ADJLoggerMock *) [ADJAdjustFactory logger]; + + [self.loggerMock test:[succeededPrefix stringByAppendingFormat:@"init"]]; + + return self; +} + +- (void)adjustSessionTrackingSucceeded:(ADJSessionSuccess *)sessionSuccessResponseData { + [self.loggerMock test:[succeededPrefix stringByAppendingFormat:@"adjustSessionTrackingSucceeded, %@", sessionSuccessResponseData]]; +} + +- (void)adjustEventTrackingSucceeded:(ADJEventSuccess *)eventSuccessResponseData { + [self.loggerMock test:[succeededPrefix stringByAppendingFormat:@"adjustEventTrackingSucceeded, %@", eventSuccessResponseData]]; +} +@end + +@interface ADJTrackingFailedDelegate() +@property (nonatomic, strong) ADJLoggerMock *loggerMock; +@end + +@implementation ADJTrackingFailedDelegate + +- (id) init { + self = [super init]; + if (self == nil) return nil; + + self.loggerMock = (ADJLoggerMock *) [ADJAdjustFactory logger]; + + [self.loggerMock test:[failedPrefix stringByAppendingFormat:@"init"]]; + + return self; +} + +- (void)adjustSessionTrackingFailed:(ADJSessionFailure *)sessionFailureResponseData { + [self.loggerMock test:[failedPrefix stringByAppendingFormat:@"adjustSessionTrackingFailed, %@", sessionFailureResponseData]]; +} + +- (void)adjustEventTrackingFailed:(ADJEventFailure *)eventFailureResponseData { + [self.loggerMock test:[failedPrefix stringByAppendingFormat:@"adjustEventTrackingFailed, %@", eventFailureResponseData]]; +} + +@end diff --git a/AdjustTests/ADJTrackingFailedDelegate.h b/AdjustTests/ADJTrackingFailedDelegate.h deleted file mode 100644 index 1287335a5..000000000 --- a/AdjustTests/ADJTrackingFailedDelegate.h +++ /dev/null @@ -1,14 +0,0 @@ -// -// ADJTrackingFailedDelegate.h -// adjust -// -// Created by Pedro Filipe on 22/01/16. -// Copyright © 2016 adjust GmbH. All rights reserved. -// - -#import -#import "Adjust.h" - -@interface ADJTrackingFailedDelegate : NSObject - -@end diff --git a/AdjustTests/ADJTrackingFailedDelegate.m b/AdjustTests/ADJTrackingFailedDelegate.m deleted file mode 100644 index d9fdf1d83..000000000 --- a/AdjustTests/ADJTrackingFailedDelegate.m +++ /dev/null @@ -1,42 +0,0 @@ -// -// ADJTrackingFailedDelegate.m -// adjust -// -// Created by Pedro Filipe on 22/01/16. -// Copyright © 2016 adjust GmbH. All rights reserved. -// - -#import "ADJTrackingFailedDelegate.h" -#import "ADJLoggerMock.h" -#import "ADJAdjustFactory.h" - -static NSString * const prefix = @"ADJTrackingFailedDelegate "; - -@interface ADJTrackingFailedDelegate() - -@property (nonatomic, strong) ADJLoggerMock *loggerMock; - -@end - -@implementation ADJTrackingFailedDelegate - -- (id) init { - self = [super init]; - if (self == nil) return nil; - - self.loggerMock = (ADJLoggerMock *) [ADJAdjustFactory logger]; - - [self.loggerMock test:[prefix stringByAppendingFormat:@"init"]]; - - return self; -} - -- (void)adjustSessionTrackingFailed:(ADJSessionFailure *)sessionFailureResponseData { - [self.loggerMock test:[prefix stringByAppendingFormat:@"adjustSessionTrackingFailed, %@", sessionFailureResponseData]]; -} - -- (void)adjustEventTrackingFailed:(ADJEventFailure *)eventFailureResponseData { - [self.loggerMock test:[prefix stringByAppendingFormat:@"adjustEventTrackingFailed, %@", eventFailureResponseData]]; -} - -@end diff --git a/AdjustTests/ADJTrackingSucceededDelegate.m b/AdjustTests/ADJTrackingSucceededDelegate.m deleted file mode 100644 index 65bacd53f..000000000 --- a/AdjustTests/ADJTrackingSucceededDelegate.m +++ /dev/null @@ -1,41 +0,0 @@ -// -// ADJTrackingSucceededDelegate.m -// adjust -// -// Created by Pedro Filipe on 22/01/16. -// Copyright © 2016 adjust GmbH. All rights reserved. -// - -#import "ADJTrackingSucceededDelegate.h" -#import "ADJLoggerMock.h" -#import "ADJAdjustFactory.h" - -static NSString * const prefix = @"ADJTrackingSucceededDelegate "; - -@interface ADJTrackingSucceededDelegate() - -@property (nonatomic, strong) ADJLoggerMock *loggerMock; - -@end - -@implementation ADJTrackingSucceededDelegate - -- (id) init { - self = [super init]; - if (self == nil) return nil; - - self.loggerMock = (ADJLoggerMock *) [ADJAdjustFactory logger]; - - [self.loggerMock test:[prefix stringByAppendingFormat:@"init"]]; - - return self; -} - -- (void)adjustSessionTrackingSucceeded:(ADJSessionSuccess *)sessionSuccessResponseData { - [self.loggerMock test:[prefix stringByAppendingFormat:@"adjustSessionTrackingSucceeded, %@", sessionSuccessResponseData]]; -} - -- (void)adjustEventTrackingSucceeded:(ADJEventSuccess *)eventSuccessResponseData { - [self.loggerMock test:[prefix stringByAppendingFormat:@"adjustEventTrackingSucceeded, %@", eventSuccessResponseData]]; -} -@end diff --git a/AdjustTests/NSURLConnection+NSURLConnectionSynchronousLoadingMocking.h b/AdjustTests/NSURLConnection+NSURLConnectionSynchronousLoadingMocking.h index 6fe4cac20..1f3d8b289 100644 --- a/AdjustTests/NSURLConnection+NSURLConnectionSynchronousLoadingMocking.h +++ b/AdjustTests/NSURLConnection+NSURLConnectionSynchronousLoadingMocking.h @@ -24,5 +24,7 @@ typedef enum { + (void)setResponseType:(ADJResponseType)responseType; + (NSURLRequest *)getLastRequest; + (void)reset; ++ (void)setTimeoutMock:(BOOL)enable; ++ (void)setWaitingTime:(double)waitingTime; @end diff --git a/AdjustTests/NSURLConnection+NSURLConnectionSynchronousLoadingMocking.m b/AdjustTests/NSURLConnection+NSURLConnectionSynchronousLoadingMocking.m index 23cc0917e..22bad95e3 100644 --- a/AdjustTests/NSURLConnection+NSURLConnectionSynchronousLoadingMocking.m +++ b/AdjustTests/NSURLConnection+NSURLConnectionSynchronousLoadingMocking.m @@ -11,6 +11,8 @@ static ADJResponseType responseTypeInternal; static NSURLRequest * lastRequest = nil; +static BOOL timeoutMockInternal = NO; +static double waitingTimeInternal = 0.0; @implementation NSURLConnection(NSURLConnectionSynchronousLoadingMock) @@ -22,6 +24,14 @@ + (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NS NSInteger statusCode = 200; NSString * sResponse; + if (timeoutMockInternal) { + [NSThread sleepForTimeInterval:10.0]; + } + + if (waitingTimeInternal != 0) { + [NSThread sleepForTimeInterval:waitingTimeInternal]; + } + if (responseTypeInternal == ADJResponseTypeNil) { return nil; } else if (responseTypeInternal == ADJResponseTypeConnError) { @@ -88,9 +98,18 @@ + (NSURLRequest *)getLastRequest return lastRequest; } ++ (void)setTimeoutMock:(BOOL)enable { + timeoutMockInternal = enable; +} ++ (void)setWaitingTime:(double)waitingTime { + waitingTimeInternal = waitingTime; +} + + (void)reset { responseTypeInternal = ADJResponseTypeNil; lastRequest = nil; + timeoutMockInternal = NO; + waitingTimeInternal = 0.0; } @end diff --git a/AdjustTests/NSURLSession+NSURLDataWithRequestMocking.h b/AdjustTests/NSURLSession+NSURLDataWithRequestMocking.h index cc72b9422..a570d78a0 100644 --- a/AdjustTests/NSURLSession+NSURLDataWithRequestMocking.h +++ b/AdjustTests/NSURLSession+NSURLDataWithRequestMocking.h @@ -26,6 +26,8 @@ typedef enum { + (void)setResponseType:(ADJSessionResponseType)responseType; + (NSURLResponse *)getLastRequest; + (void)reset; ++ (void)setTimeoutMock:(BOOL)enable; ++ (void)setWaitingTime:(double)waitingTime; @end diff --git a/AdjustTests/NSURLSession+NSURLDataWithRequestMocking.m b/AdjustTests/NSURLSession+NSURLDataWithRequestMocking.m index 2496aa4dd..775d3798a 100644 --- a/AdjustTests/NSURLSession+NSURLDataWithRequestMocking.m +++ b/AdjustTests/NSURLSession+NSURLDataWithRequestMocking.m @@ -17,6 +17,9 @@ static NSURLResponse * completionResponse = nil; static NSError * completionError = nil; +static BOOL timeoutMockInternal = NO; +static double waitingTimeInternal = 0.0; + static void (^completionDelegate) (NSData * data, NSURLResponse * response, NSError * error) = nil; @implementation NSURLSession(NSURLDataWithRequestMocking) @@ -36,6 +39,14 @@ - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request completionData = nil; completionError = nil; + if (timeoutMockInternal) { + [NSThread sleepForTimeInterval:10.0]; + } + + if (waitingTimeInternal != 0) { + [NSThread sleepForTimeInterval:waitingTimeInternal]; + } + if (sessionResponseTypeInternal == ADJSessionResponseTypeNil) { } else if (sessionResponseTypeInternal == ADJSessionResponseTypeConnError) { @@ -81,6 +92,15 @@ + (NSURLRequest *)getLastRequest + (void)reset { sessionResponseTypeInternal = ADJSessionResponseTypeNil; lastRequest = nil; + timeoutMockInternal = NO; + waitingTimeInternal = 0.0; +} + ++ (void)setTimeoutMock:(BOOL)enable { + timeoutMockInternal = enable; +} ++ (void)setWaitingTime:(double)waitingTime { + waitingTimeInternal = waitingTime; } @end diff --git a/README.md b/README.md index 1637fc9ed..58f718a1d 100644 --- a/README.md +++ b/README.md @@ -28,12 +28,14 @@ This is the iOS SDK of adjust™. You can read more about adjust™ at * [Enable your iOS app to handle Universal Links](#ulinks-ios-app) * [Support deep linking for all iOS versions supported by the adjust SDK](#ulinks-support-all) * [Event buffering](#step8) - * [Attribution callback](#step9) - * [Callbacks for tracked events and sessions](#step10) - * [Disable tracking](#step11) - * [Offline mode](#step12) - * [Device IDs](#step13) - * [Push token](#step14) + * [Send in the background](#step9) + * [Attribution callback](#step10) + * [Callbacks for tracked events and sessions](#step11) + * [Callbacks for deferred deeplinks](#step12) + * [Disable tracking](#step13) + * [Offline mode](#step14) + * [Device IDs](#step15) + * [Push token](#step16) * [Troubleshooting](#troubleshooting) * [Issues with delayed SDK initialisation](#ts-delayed-init) * [I'm seeing "Adjust requires ARC" error](#ts-arc) @@ -60,13 +62,13 @@ If you're using [CocoaPods][cocoapods], you can add the following line to your `Podfile` and continue with [step 4](#step4): ```ruby -pod 'Adjust', '~> 4.6.0' +pod 'Adjust', '~> 4.7.0' ``` or: ```ruby -pod 'Adjust', :git => 'https://github.com/adjust/ios_sdk.git', :tag => 'v4.6.0' +pod 'Adjust', :git => 'https://github.com/adjust/ios_sdk.git', :tag => 'v4.7.0' ``` If you're using [Carthage][carthage], you can add following line to your `Cartfile` @@ -482,7 +484,16 @@ event buffering with your `ADJConfig` instance: [adjustConfig setEventBufferingEnabled:YES]; ``` -### 9. Attribution callback +### 9. Send in the background + +The default behaviour of the adjust SDK is to pause sending HTTP requests while the app is on the background. +You can change this in your `AdjustConfig` instance: + +```objc +[adjustConfig setSendInBackground:YES]; +``` + +### 10. Attribution callback You can register a delegate callback to be notified of tracker attribution changes. Due to the different sources considered for attribution, this @@ -532,13 +543,13 @@ Here is a quick summary of its properties: - `NSString creative` the creative grouping level of the current install. - `NSString clickLabel` the click label of the current install. -### 10. Callbacks for tracked events and sessions +### 11. Callbacks for tracked events and sessions You can register a delegate callback to be notified of successful and failed tracked events and/or sessions. -The same optional protocol `AdjustDelegate` used for the attribution changed callback -[here](#9-the-attribution-callback) is used. +The same optional protocol `AdjustDelegate` used for the [attribution changed callback] +(#step10) is used. Follow the same steps and implement the following delegate callback function for successful tracked events: @@ -585,7 +596,33 @@ And both event and session failed objects also contain: - `BOOL willRetry` indicates there will be an attempt to resend the package at a later time. -### 11. Disable tracking +### 12. Callbacks for deferred deeplinks + +You can register a delegate callback to be notified before a deferred deeplink is opened and decide if the adjust SDK will open it. + +The same optional protocol `AdjustDelegate` used for the [attribution changed callback] +(#step10) and for [tracked events and sessions](#step11) is used. + +Follow the same steps and implement the following delegate callback function for +deferred deeplinks: + +```objc +// evaluate deeplink to be launched +- (void)adjustDeeplinkResponse:(NSURL *)deeplink { + // ... + if ([self allowAdjustSDKToOpenDeeplink:deeplink]) { + return YES; + } else { + return NO; + } +} +``` + +The callback function will be called after the SDK receives a deffered deeplink from ther server and before open it. +Within the callback function you have access to the deeplink and the boolean that you return determines if the SDK will launch the deeplink. +You could, for example, not allow the SDK open the deeplink at the moment, save it, and open it yourself later. + +### 13. Disable tracking You can disable the adjust SDK from tracking any activities of the current device by calling `setEnabled` with parameter `NO`. **This setting is remembered @@ -599,7 +636,7 @@ between sessions**, but it can only be activated after the first session. the function `isEnabled`. It is always possible to activate the adjust SDK by invoking `setEnabled` with the enabled parameter as `YES`. -### 12. Offline mode +### 14. Offline mode You can put the adjust SDK in offline mode to suspend transmission to our servers while retaining tracked data to be sent later. While in offline mode, all information is saved @@ -619,7 +656,7 @@ Unlike disabling tracking, this setting is *not remembered* bettween sessions. This means that the SDK is in online mode whenever it is started, even if the app was terminated in offline mode. -### 13. Device IDs +### 15. Device IDs Certain services (such as Google Analytics) require you to coordinate Device and Client IDs in order to prevent duplicate reporting. @@ -630,7 +667,7 @@ To obtain the device identifier IDFA, call the function `idfa`: NSString *idfa = [Adjust idfa]; ``` -### 14. Push token +### 16. Push token To send us the push notification token, then add the following call to `Adjust` in the `didRegisterForRemoteNotificationsWithDeviceToken` of your app delegate: @@ -658,8 +695,8 @@ If you decide to perform any of these actions: * [Event tracking](#step6) * [Deep link reattributions](#step7) -* [Disable tracking](#step11) -* [Offline mode](#step12) +* [Disable tracking](#step13) +* [Offline mode](#step14) before initialising the SDK, `they won't be performed`. @@ -817,7 +854,7 @@ package looking something like this: ``` [Adjust]d: Added package 1 (click) [Adjust]v: Path: /sdk_click -[Adjust]v: ClientSdk: ios4.6.0 +[Adjust]v: ClientSdk: ios4.7.0 [Adjust]v: Parameters: [Adjust]v: app_token {YourAppToken} [Adjust]v: created_at 2016-04-15T14:25:51.676Z+0200 diff --git a/VERSION b/VERSION index 6016e8add..f6cdf4098 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.6.0 +4.7.0 diff --git a/doc/migrate.md b/doc/migrate.md index ae76e0849..0e207a8d9 100644 --- a/doc/migrate.md +++ b/doc/migrate.md @@ -1,4 +1,4 @@ -## Migrate your adjust SDK for iOS to v4.6.0 from v3.4.0 +## Migrate your adjust SDK for iOS to v4.7.0 from v3.4.0 ### Initial setup diff --git a/examples/AdjustExample-Swift/AdjustExample-Swift.xcodeproj/project.pbxproj b/examples/AdjustExample-Swift/AdjustExample-Swift.xcodeproj/project.pbxproj index ebcd321e5..e0ed16f36 100644 --- a/examples/AdjustExample-Swift/AdjustExample-Swift.xcodeproj/project.pbxproj +++ b/examples/AdjustExample-Swift/AdjustExample-Swift.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 96164DAF1CCA4E73009431AB /* ADJBackoffStrategy.m in Sources */ = {isa = PBXBuildFile; fileRef = 96164DAA1CCA4E73009431AB /* ADJBackoffStrategy.m */; }; + 96164DB01CCA4E73009431AB /* ADJSdkClickHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 96164DAC1CCA4E73009431AB /* ADJSdkClickHandler.m */; }; + 96164DB11CCA4E73009431AB /* ADJSystemProfile.m in Sources */ = {isa = PBXBuildFile; fileRef = 96164DAE1CCA4E73009431AB /* ADJSystemProfile.m */; }; 9DF7A9C61CB4ECA600D3591F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DF7A9C51CB4ECA600D3591F /* AppDelegate.swift */; }; 9DF7A9C81CB4ECA600D3591F /* ViewControllerSwift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9DF7A9C71CB4ECA600D3591F /* ViewControllerSwift.swift */; }; 9DF7A9CB1CB4ECA600D3591F /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9DF7A9C91CB4ECA600D3591F /* Main.storyboard */; }; @@ -42,6 +45,12 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 96164DA91CCA4E73009431AB /* ADJBackoffStrategy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJBackoffStrategy.h; sourceTree = ""; }; + 96164DAA1CCA4E73009431AB /* ADJBackoffStrategy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJBackoffStrategy.m; sourceTree = ""; }; + 96164DAB1CCA4E73009431AB /* ADJSdkClickHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJSdkClickHandler.h; sourceTree = ""; }; + 96164DAC1CCA4E73009431AB /* ADJSdkClickHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJSdkClickHandler.m; sourceTree = ""; }; + 96164DAD1CCA4E73009431AB /* ADJSystemProfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJSystemProfile.h; sourceTree = ""; }; + 96164DAE1CCA4E73009431AB /* ADJSystemProfile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJSystemProfile.m; sourceTree = ""; }; 9DF7A9C21CB4ECA600D3591F /* AdjustExample-Swift.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "AdjustExample-Swift.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 9DF7A9C51CB4ECA600D3591F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 9DF7A9C71CB4ECA600D3591F /* ViewControllerSwift.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerSwift.swift; sourceTree = ""; }; @@ -150,6 +159,12 @@ 9DF7A9E21CB4F0DB00D3591F /* Adjust */ = { isa = PBXGroup; children = ( + 96164DA91CCA4E73009431AB /* ADJBackoffStrategy.h */, + 96164DAA1CCA4E73009431AB /* ADJBackoffStrategy.m */, + 96164DAB1CCA4E73009431AB /* ADJSdkClickHandler.h */, + 96164DAC1CCA4E73009431AB /* ADJSdkClickHandler.m */, + 96164DAD1CCA4E73009431AB /* ADJSystemProfile.h */, + 96164DAE1CCA4E73009431AB /* ADJSystemProfile.m */, 9DF7A9E31CB4F0DB00D3591F /* ADJActivityHandler.h */, 9DF7A9E41CB4F0DB00D3591F /* ADJActivityHandler.m */, 9DF7A9E51CB4F0DB00D3591F /* ADJActivityKind.h */, @@ -293,6 +308,7 @@ 9DF7AA2E1CB4F0DB00D3591F /* ADJSessionSuccess.m in Sources */, 9DF7A9C81CB4ECA600D3591F /* ViewControllerSwift.swift in Sources */, 9DF7AA291CB4F0DB00D3591F /* ADJPackageBuilder.m in Sources */, + 96164DB11CCA4E73009431AB /* ADJSystemProfile.m in Sources */, 9DF7AA211CB4F0DB00D3591F /* ADJAttribution.m in Sources */, 9DF7AA251CB4F0DB00D3591F /* ADJEvent.m in Sources */, 9DF7AA311CB4F0DB00D3591F /* Adjust.m in Sources */, @@ -300,7 +316,9 @@ 9DF7AA321CB4F0DB00D3591F /* ADJUtil.m in Sources */, 9DF7AA261CB4F0DB00D3591F /* ADJEventFailure.m in Sources */, 9DF7AA221CB4F0DB00D3591F /* ADJAttributionHandler.m in Sources */, + 96164DB01CCA4E73009431AB /* ADJSdkClickHandler.m in Sources */, 9DF7AA301CB4F0DB00D3591F /* ADJTimerOnce.m in Sources */, + 96164DAF1CCA4E73009431AB /* ADJBackoffStrategy.m in Sources */, 9DF7AA241CB4F0DB00D3591F /* ADJDeviceInfo.m in Sources */, 9DF7AA1F1CB4F0DB00D3591F /* UIDevice+ADJAdditions.m in Sources */, 9DF7AA281CB4F0DB00D3591F /* ADJLogger.m in Sources */, diff --git a/examples/AdjustExample-Swift/AdjustExample-Swift/AppDelegate.swift b/examples/AdjustExample-Swift/AdjustExample-Swift/AppDelegate.swift index 03e339bed..c6b34f0fa 100644 --- a/examples/AdjustExample-Swift/AdjustExample-Swift/AppDelegate.swift +++ b/examples/AdjustExample-Swift/AdjustExample-Swift/AppDelegate.swift @@ -27,6 +27,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate, AdjustDelegate { // set default tracker // adjustConfig.defaultTracker = "{TrackerToken}" + // send in the background + // adjustConfig.sendInBackground = true + // set an attribution delegate adjustConfig.delegate = self @@ -61,6 +64,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate, AdjustDelegate { NSLog("adjust session failure %@", sessionFailureResponseData) } + func adjustDeeplinkResponse(deeplink: NSURL!) -> Bool { + return true + } + func applicationWillResignActive(application: UIApplication) { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. diff --git a/examples/AdjustExample-iOS/AdjustExample-iOS.xcodeproj/project.pbxproj b/examples/AdjustExample-iOS/AdjustExample-iOS.xcodeproj/project.pbxproj index f51f9ab5e..93bfd31e7 100644 --- a/examples/AdjustExample-iOS/AdjustExample-iOS.xcodeproj/project.pbxproj +++ b/examples/AdjustExample-iOS/AdjustExample-iOS.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 96164D941CCA4E1C009431AB /* ADJBackoffStrategy.m in Sources */ = {isa = PBXBuildFile; fileRef = 96164D8F1CCA4E1C009431AB /* ADJBackoffStrategy.m */; }; + 96164D951CCA4E1C009431AB /* ADJSdkClickHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 96164D911CCA4E1C009431AB /* ADJSdkClickHandler.m */; }; + 96164D961CCA4E1C009431AB /* ADJSystemProfile.m in Sources */ = {isa = PBXBuildFile; fileRef = 96164D931CCA4E1C009431AB /* ADJSystemProfile.m */; }; 963909411BCBFCF300A2E8A4 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 963909401BCBFCF300A2E8A4 /* main.m */; }; 963909441BCBFCF300A2E8A4 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 963909431BCBFCF300A2E8A4 /* AppDelegate.m */; }; 9639094A1BCBFCF300A2E8A4 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 963909481BCBFCF300A2E8A4 /* Main.storyboard */; }; @@ -46,6 +49,12 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 96164D8E1CCA4E1C009431AB /* ADJBackoffStrategy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJBackoffStrategy.h; sourceTree = ""; }; + 96164D8F1CCA4E1C009431AB /* ADJBackoffStrategy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJBackoffStrategy.m; sourceTree = ""; }; + 96164D901CCA4E1C009431AB /* ADJSdkClickHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJSdkClickHandler.h; sourceTree = ""; }; + 96164D911CCA4E1C009431AB /* ADJSdkClickHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJSdkClickHandler.m; sourceTree = ""; }; + 96164D921CCA4E1C009431AB /* ADJSystemProfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJSystemProfile.h; sourceTree = ""; }; + 96164D931CCA4E1C009431AB /* ADJSystemProfile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJSystemProfile.m; sourceTree = ""; }; 9639093C1BCBFCF300A2E8A4 /* AdjustExample-iOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "AdjustExample-iOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 963909401BCBFCF300A2E8A4 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 963909421BCBFCF300A2E8A4 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; @@ -178,6 +187,12 @@ 963909681BCBFF8D00A2E8A4 /* Adjust */ = { isa = PBXGroup; children = ( + 96164D8E1CCA4E1C009431AB /* ADJBackoffStrategy.h */, + 96164D8F1CCA4E1C009431AB /* ADJBackoffStrategy.m */, + 96164D901CCA4E1C009431AB /* ADJSdkClickHandler.h */, + 96164D911CCA4E1C009431AB /* ADJSdkClickHandler.m */, + 96164D921CCA4E1C009431AB /* ADJSystemProfile.h */, + 96164D931CCA4E1C009431AB /* ADJSystemProfile.m */, 96E423C61C85F3B50006729A /* ADJEventFailure.h */, 96E423C71C85F3B50006729A /* ADJEventFailure.m */, 96E423C81C85F3B50006729A /* ADJEventSuccess.h */, @@ -328,6 +343,7 @@ 96BA940B1C3D57FD00C96245 /* ADJSessionFailure.m in Sources */, 9639099D1BCBFF8D00A2E8A4 /* ADJAttributionHandler.m in Sources */, 963909A21BCBFF8D00A2E8A4 /* ADJPackageBuilder.m in Sources */, + 96164D961CCA4E1C009431AB /* ADJSystemProfile.m in Sources */, 963909A51BCBFF8D00A2E8A4 /* ADJTimerCycle.m in Sources */, 963909A61BCBFF8D00A2E8A4 /* ADJTimerOnce.m in Sources */, 9DC95F2A1C10515300138E4B /* Constants.m in Sources */, @@ -343,6 +359,7 @@ 963909A31BCBFF8D00A2E8A4 /* ADJPackageHandler.m in Sources */, 963909981BCBFF8D00A2E8A4 /* NSData+ADJAdditions.m in Sources */, 963909A41BCBFF8D00A2E8A4 /* ADJRequestHandler.m in Sources */, + 96164D951CCA4E1C009431AB /* ADJSdkClickHandler.m in Sources */, 963909941BCBFF8D00A2E8A4 /* ADJActivityHandler.m in Sources */, 963909991BCBFF8D00A2E8A4 /* NSString+ADJAdditions.m in Sources */, 963909A71BCBFF8D00A2E8A4 /* Adjust.m in Sources */, @@ -351,6 +368,7 @@ 9639099B1BCBFF8D00A2E8A4 /* ADJAdjustFactory.m in Sources */, 963909411BCBFCF300A2E8A4 /* main.m in Sources */, 96E423CB1C85F3B50006729A /* ADJEventSuccess.m in Sources */, + 96164D941CCA4E1C009431AB /* ADJBackoffStrategy.m in Sources */, 9639099C1BCBFF8D00A2E8A4 /* ADJAttribution.m in Sources */, 9DC95F261C104CEF00138E4B /* ViewControlleriOS.m in Sources */, ); diff --git a/examples/AdjustExample-iOS/AdjustExample-iOS/AppDelegate.m b/examples/AdjustExample-iOS/AdjustExample-iOS/AppDelegate.m index e1ed91fb3..36a485a5c 100644 --- a/examples/AdjustExample-iOS/AdjustExample-iOS/AppDelegate.m +++ b/examples/AdjustExample-iOS/AdjustExample-iOS/AppDelegate.m @@ -8,14 +8,13 @@ #import "Constants.h" #import "AppDelegate.h" - +#import "ADJSystemProfile.h" @interface AppDelegate () @end @implementation AppDelegate - - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch. @@ -33,6 +32,9 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( // set default tracker //[adjustConfig setDefaultTracker:@"{TrackerToken}"]; + // send in the background + //[adjustConfig setSendInBackground:YES]; + // set an attribution delegate [adjustConfig setDelegate:self]; @@ -43,7 +45,7 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( // disable the SDK //[Adjust setEnabled:NO]; - + return YES; } @@ -74,6 +76,11 @@ - (void)adjustSessionTrackingFailed:(ADJSessionFailure *)sessionFailureResponseD NSLog(@"adjust session failure %@", sessionFailureResponseData); } +// evaluate deeplink to be launched +- (BOOL)adjustDeeplinkResponse:(NSURL *)deeplink { + return YES; +} + - (void)applicationWillResignActive:(UIApplication *)application { // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. diff --git a/examples/AdjustExample-iWatch/AdjustExample-iWatch.xcodeproj/project.pbxproj b/examples/AdjustExample-iWatch/AdjustExample-iWatch.xcodeproj/project.pbxproj index 709bde6fb..01ca1653f 100644 --- a/examples/AdjustExample-iWatch/AdjustExample-iWatch.xcodeproj/project.pbxproj +++ b/examples/AdjustExample-iWatch/AdjustExample-iWatch.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 96164DA61CCA4E54009431AB /* ADJBackoffStrategy.m in Sources */ = {isa = PBXBuildFile; fileRef = 96164DA11CCA4E54009431AB /* ADJBackoffStrategy.m */; }; + 96164DA71CCA4E54009431AB /* ADJSdkClickHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 96164DA31CCA4E54009431AB /* ADJSdkClickHandler.m */; }; + 96164DA81CCA4E54009431AB /* ADJSystemProfile.m in Sources */ = {isa = PBXBuildFile; fileRef = 96164DA51CCA4E54009431AB /* ADJSystemProfile.m */; }; 9DF7AC191CB4FEDB00D3591F /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DF7AC181CB4FEDB00D3591F /* main.m */; }; 9DF7AC1C1CB4FEDB00D3591F /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DF7AC1B1CB4FEDB00D3591F /* AppDelegate.m */; }; 9DF7AC1F1CB4FEDB00D3591F /* ViewControllerWatch.m in Sources */ = {isa = PBXBuildFile; fileRef = 9DF7AC1E1CB4FEDB00D3591F /* ViewControllerWatch.m */; }; @@ -99,6 +102,12 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 96164DA01CCA4E54009431AB /* ADJBackoffStrategy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJBackoffStrategy.h; sourceTree = ""; }; + 96164DA11CCA4E54009431AB /* ADJBackoffStrategy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJBackoffStrategy.m; sourceTree = ""; }; + 96164DA21CCA4E54009431AB /* ADJSdkClickHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJSdkClickHandler.h; sourceTree = ""; }; + 96164DA31CCA4E54009431AB /* ADJSdkClickHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJSdkClickHandler.m; sourceTree = ""; }; + 96164DA41CCA4E54009431AB /* ADJSystemProfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJSystemProfile.h; sourceTree = ""; }; + 96164DA51CCA4E54009431AB /* ADJSystemProfile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJSystemProfile.m; sourceTree = ""; }; 9DF7AC141CB4FEDB00D3591F /* AdjustExample-iWatch.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "AdjustExample-iWatch.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 9DF7AC181CB4FEDB00D3591F /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 9DF7AC1A1CB4FEDB00D3591F /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; @@ -287,6 +296,12 @@ 9DF7AC611CB4FF1800D3591F /* Adjust */ = { isa = PBXGroup; children = ( + 96164DA01CCA4E54009431AB /* ADJBackoffStrategy.h */, + 96164DA11CCA4E54009431AB /* ADJBackoffStrategy.m */, + 96164DA21CCA4E54009431AB /* ADJSdkClickHandler.h */, + 96164DA31CCA4E54009431AB /* ADJSdkClickHandler.m */, + 96164DA41CCA4E54009431AB /* ADJSystemProfile.h */, + 96164DA51CCA4E54009431AB /* ADJSystemProfile.m */, 9DF7AC621CB4FF1800D3591F /* ADJActivityHandler.h */, 9DF7AC631CB4FF1800D3591F /* ADJActivityHandler.m */, 9DF7AC641CB4FF1800D3591F /* ADJActivityKind.h */, @@ -501,6 +516,7 @@ 9DF7ACA91CB4FF1800D3591F /* ADJPackageHandler.m in Sources */, 9DF7AC991CB4FF1800D3591F /* ADJActivityKind.m in Sources */, 9DF7AC9E1CB4FF1800D3591F /* UIDevice+ADJAdditions.m in Sources */, + 96164DA81CCA4E54009431AB /* ADJSystemProfile.m in Sources */, 9DF7ACB81CB4FF6400D3591F /* AdjustTrackingHelper.m in Sources */, 9DF7AC9B1CB4FF1800D3591F /* ADJActivityState.m in Sources */, 9DF7AC9A1CB4FF1800D3591F /* ADJActivityPackage.m in Sources */, @@ -516,6 +532,7 @@ 9DF7AC9D1CB4FF1800D3591F /* NSString+ADJAdditions.m in Sources */, 9DF7ACA21CB4FF1800D3591F /* ADJConfig.m in Sources */, 9DF7AC1C1CB4FEDB00D3591F /* AppDelegate.m in Sources */, + 96164DA71CCA4E54009431AB /* ADJSdkClickHandler.m in Sources */, 9DF7ACAC1CB4FF1800D3591F /* ADJSessionFailure.m in Sources */, 9DF7ACAB1CB4FF1800D3591F /* ADJResponseData.m in Sources */, 9DF7ACA71CB4FF1800D3591F /* ADJLogger.m in Sources */, @@ -524,6 +541,7 @@ 9DF7AC9F1CB4FF1800D3591F /* ADJAdjustFactory.m in Sources */, 9DF7ACB71CB4FF6400D3591F /* AdjustLoggingHelper.m in Sources */, 9DF7AC191CB4FEDB00D3591F /* main.m in Sources */, + 96164DA61CCA4E54009431AB /* ADJBackoffStrategy.m in Sources */, 9DF7ACAE1CB4FF1800D3591F /* ADJTimerCycle.m in Sources */, 9DF7ACA01CB4FF1800D3591F /* ADJAttribution.m in Sources */, ); diff --git a/examples/AdjustExample-tvOS/AdjustExample-tvOS.xcodeproj/project.pbxproj b/examples/AdjustExample-tvOS/AdjustExample-tvOS.xcodeproj/project.pbxproj index e7a778e97..18c2bbb6a 100644 --- a/examples/AdjustExample-tvOS/AdjustExample-tvOS.xcodeproj/project.pbxproj +++ b/examples/AdjustExample-tvOS/AdjustExample-tvOS.xcodeproj/project.pbxproj @@ -7,6 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 96164D9D1CCA4E3A009431AB /* ADJBackoffStrategy.m in Sources */ = {isa = PBXBuildFile; fileRef = 96164D981CCA4E3A009431AB /* ADJBackoffStrategy.m */; }; + 96164D9E1CCA4E3A009431AB /* ADJSdkClickHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 96164D9A1CCA4E3A009431AB /* ADJSdkClickHandler.m */; }; + 96164D9F1CCA4E3A009431AB /* ADJSystemProfile.m in Sources */ = {isa = PBXBuildFile; fileRef = 96164D9C1CCA4E3A009431AB /* ADJSystemProfile.m */; }; 963909B71BCC0D8300A2E8A4 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 963909B61BCC0D8300A2E8A4 /* main.m */; }; 963909BA1BCC0D8300A2E8A4 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 963909B91BCC0D8300A2E8A4 /* AppDelegate.m */; }; 963909BD1BCC0D8300A2E8A4 /* ViewControllertvOS.m in Sources */ = {isa = PBXBuildFile; fileRef = 963909BC1BCC0D8300A2E8A4 /* ViewControllertvOS.m */; }; @@ -45,6 +48,12 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 96164D971CCA4E3A009431AB /* ADJBackoffStrategy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJBackoffStrategy.h; sourceTree = ""; }; + 96164D981CCA4E3A009431AB /* ADJBackoffStrategy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJBackoffStrategy.m; sourceTree = ""; }; + 96164D991CCA4E3A009431AB /* ADJSdkClickHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJSdkClickHandler.h; sourceTree = ""; }; + 96164D9A1CCA4E3A009431AB /* ADJSdkClickHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJSdkClickHandler.m; sourceTree = ""; }; + 96164D9B1CCA4E3A009431AB /* ADJSystemProfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ADJSystemProfile.h; sourceTree = ""; }; + 96164D9C1CCA4E3A009431AB /* ADJSystemProfile.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ADJSystemProfile.m; sourceTree = ""; }; 963909B21BCC0D8300A2E8A4 /* AdjustExample-tvOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "AdjustExample-tvOS.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 963909B61BCC0D8300A2E8A4 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 963909B81BCC0D8300A2E8A4 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; @@ -174,6 +183,12 @@ 9DF7AA341CB4F52400D3591F /* Adjust */ = { isa = PBXGroup; children = ( + 96164D971CCA4E3A009431AB /* ADJBackoffStrategy.h */, + 96164D981CCA4E3A009431AB /* ADJBackoffStrategy.m */, + 96164D991CCA4E3A009431AB /* ADJSdkClickHandler.h */, + 96164D9A1CCA4E3A009431AB /* ADJSdkClickHandler.m */, + 96164D9B1CCA4E3A009431AB /* ADJSystemProfile.h */, + 96164D9C1CCA4E3A009431AB /* ADJSystemProfile.m */, 9DF7AA351CB4F52400D3591F /* ADJActivityHandler.h */, 9DF7AA361CB4F52400D3591F /* ADJActivityHandler.m */, 9DF7AA371CB4F52400D3591F /* ADJActivityKind.h */, @@ -324,6 +339,7 @@ 9DF7AA731CB4F52500D3591F /* ADJAttribution.m in Sources */, 9DF7AA831CB4F52500D3591F /* Adjust.m in Sources */, 9DF7AA6E1CB4F52500D3591F /* ADJActivityState.m in Sources */, + 96164D9F1CCA4E3A009431AB /* ADJSystemProfile.m in Sources */, 9DC95F2F1C10596500138E4B /* Constants.m in Sources */, 9DF7AA7D1CB4F52500D3591F /* ADJRequestHandler.m in Sources */, 9DF7AA7C1CB4F52500D3591F /* ADJPackageHandler.m in Sources */, @@ -339,6 +355,7 @@ 9DF7AA6C1CB4F52500D3591F /* ADJActivityKind.m in Sources */, 9DF7AA791CB4F52500D3591F /* ADJEventSuccess.m in Sources */, 9DC95F321C105B4C00138E4B /* URLRequest.m in Sources */, + 96164D9E1CCA4E3A009431AB /* ADJSdkClickHandler.m in Sources */, 9DF7AA741CB4F52500D3591F /* ADJAttributionHandler.m in Sources */, 9DF7AA841CB4F52500D3591F /* ADJUtil.m in Sources */, 9DF7AA751CB4F52500D3591F /* ADJConfig.m in Sources */, @@ -347,6 +364,7 @@ 9DF7AA781CB4F52500D3591F /* ADJEventFailure.m in Sources */, 9DF7AA6F1CB4F52500D3591F /* NSData+ADJAdditions.m in Sources */, 9DF7AA7A1CB4F52500D3591F /* ADJLogger.m in Sources */, + 96164D9D1CCA4E3A009431AB /* ADJBackoffStrategy.m in Sources */, 9DF7AA771CB4F52500D3591F /* ADJEvent.m in Sources */, 9DF7AA6D1CB4F52500D3591F /* ADJActivityPackage.m in Sources */, ); diff --git a/examples/AdjustExample-tvOS/AdjustExample-tvOS/AppDelegate.m b/examples/AdjustExample-tvOS/AdjustExample-tvOS/AppDelegate.m index 566da508b..1d14ffedb 100644 --- a/examples/AdjustExample-tvOS/AdjustExample-tvOS/AppDelegate.m +++ b/examples/AdjustExample-tvOS/AdjustExample-tvOS/AppDelegate.m @@ -32,6 +32,9 @@ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:( // set default tracker //[adjustConfig setDefaultTracker:@"{TrackerToken}"]; + // send in the background + //[adjustConfig setSendInBackground:YES]; + // set an attribution delegate [adjustConfig setDelegate:self]; @@ -66,6 +69,11 @@ - (void)adjustSessionTrackingFailed:(ADJSessionFailure *)sessionFailureResponseD NSLog(@"adjust session failure %@", sessionFailureResponseData); } +// evaluate deeplink to be launched +- (BOOL)adjustDeeplinkResponse:(NSURL *)deeplink { + return YES; +} + - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation { [Adjust appWillOpenUrl:url];