diff --git a/Adjust/build.gradle b/Adjust/build.gradle index 810887ec6..3e2ce2b87 100644 --- a/Adjust/build.gradle +++ b/Adjust/build.gradle @@ -9,7 +9,7 @@ ext { coreMinSdkVersion = 21 coreCompileSdkVersion = 36 coreTargetSdkVersion = 36 - coreVersionName = '5.4.6' + coreVersionName = '5.5.0' defaultVersionCode = 1 webbridgeMinSdkVersion = 21 samsungReferrerMinSdkVersion = 21 diff --git a/Adjust/examples/example-app-fbpixel/build.gradle b/Adjust/examples/example-app-fbpixel/build.gradle index 022cd12aa..544d46250 100644 --- a/Adjust/examples/example-app-fbpixel/build.gradle +++ b/Adjust/examples/example-app-fbpixel/build.gradle @@ -35,5 +35,5 @@ dependencies { implementation project(':sdk-core') implementation project(':plugins:sdk-plugin-webbridge') - implementation 'com.adjust.signature:adjust-android-signature:3.61.0' + implementation 'com.adjust.signature:adjust-android-signature:3.62.0' } diff --git a/Adjust/examples/example-app-java/build.gradle b/Adjust/examples/example-app-java/build.gradle index d8eb9aaac..faa74fd33 100644 --- a/Adjust/examples/example-app-java/build.gradle +++ b/Adjust/examples/example-app-java/build.gradle @@ -38,5 +38,5 @@ dependencies { implementation 'com.google.android.gms:play-services-appset:16.1.0' implementation project(':sdk-core') - implementation 'com.adjust.signature:adjust-android-signature:3.61.0' + implementation 'com.adjust.signature:adjust-android-signature:3.62.0' } diff --git a/Adjust/examples/example-app-keyboard/build.gradle b/Adjust/examples/example-app-keyboard/build.gradle index 71c42ea86..d1e5d13b6 100644 --- a/Adjust/examples/example-app-keyboard/build.gradle +++ b/Adjust/examples/example-app-keyboard/build.gradle @@ -38,5 +38,5 @@ dependencies { implementation 'com.google.android.gms:play-services-appset:16.1.0' implementation project(':sdk-core') - implementation 'com.adjust.signature:adjust-android-signature:3.61.0' + implementation 'com.adjust.signature:adjust-android-signature:3.62.0' } diff --git a/Adjust/examples/example-app-kotlin/build.gradle b/Adjust/examples/example-app-kotlin/build.gradle index 985d785bf..edeed92c0 100644 --- a/Adjust/examples/example-app-kotlin/build.gradle +++ b/Adjust/examples/example-app-kotlin/build.gradle @@ -41,5 +41,5 @@ dependencies { implementation project(':sdk-core') - implementation 'com.adjust.signature:adjust-android-signature:3.61.0' + implementation 'com.adjust.signature:adjust-android-signature:3.62.0' } diff --git a/Adjust/examples/example-app-tv/build.gradle b/Adjust/examples/example-app-tv/build.gradle index d8eb9aaac..faa74fd33 100755 --- a/Adjust/examples/example-app-tv/build.gradle +++ b/Adjust/examples/example-app-tv/build.gradle @@ -38,5 +38,5 @@ dependencies { implementation 'com.google.android.gms:play-services-appset:16.1.0' implementation project(':sdk-core') - implementation 'com.adjust.signature:adjust-android-signature:3.61.0' + implementation 'com.adjust.signature:adjust-android-signature:3.62.0' } diff --git a/Adjust/examples/example-app-webbridge/build.gradle b/Adjust/examples/example-app-webbridge/build.gradle index 31a6c3385..d3cc31595 100644 --- a/Adjust/examples/example-app-webbridge/build.gradle +++ b/Adjust/examples/example-app-webbridge/build.gradle @@ -33,5 +33,5 @@ dependencies { implementation project(':sdk-core') implementation project(':plugins:sdk-plugin-webbridge') - implementation 'com.adjust.signature:adjust-android-signature:3.61.0' + implementation 'com.adjust.signature:adjust-android-signature:3.62.0' } diff --git a/Adjust/plugins/sdk-plugin-huawei-referrer/build.gradle b/Adjust/plugins/sdk-plugin-huawei-referrer/build.gradle index 5a9787c15..bcd827071 100644 --- a/Adjust/plugins/sdk-plugin-huawei-referrer/build.gradle +++ b/Adjust/plugins/sdk-plugin-huawei-referrer/build.gradle @@ -27,7 +27,7 @@ dependencies { // Add SDK via module. compileOnly project(':sdk-core') // Add SDK via Maven. - // implementation 'com.adjust.sdk:adjust-android:5.4.6' + // implementation 'com.adjust.sdk:adjust-android:5.5.0' } // read local properties diff --git a/Adjust/plugins/sdk-plugin-imei/build.gradle b/Adjust/plugins/sdk-plugin-imei/build.gradle index c995d8ad0..21ef749e8 100644 --- a/Adjust/plugins/sdk-plugin-imei/build.gradle +++ b/Adjust/plugins/sdk-plugin-imei/build.gradle @@ -30,7 +30,7 @@ dependencies { // Add SDK via module. compileOnly project(':sdk-core') // Add SDK via Maven. - // implementation 'com.adjust.sdk:adjust-android:5.4.6' + // implementation 'com.adjust.sdk:adjust-android:5.5.0' } // read local properties diff --git a/Adjust/plugins/sdk-plugin-meta-referrer/build.gradle b/Adjust/plugins/sdk-plugin-meta-referrer/build.gradle index fa26fd195..3d9c18c8b 100644 --- a/Adjust/plugins/sdk-plugin-meta-referrer/build.gradle +++ b/Adjust/plugins/sdk-plugin-meta-referrer/build.gradle @@ -27,7 +27,7 @@ dependencies { // Add SDK via module. compileOnly project(':sdk-core') // Add SDK via Maven. - // implementation 'com.adjust.sdk:adjust-android:5.4.6' + // implementation 'com.adjust.sdk:adjust-android:5.5.0' } // read local properties diff --git a/Adjust/plugins/sdk-plugin-oaid/build.gradle b/Adjust/plugins/sdk-plugin-oaid/build.gradle index 3cebb2859..1da522ce8 100644 --- a/Adjust/plugins/sdk-plugin-oaid/build.gradle +++ b/Adjust/plugins/sdk-plugin-oaid/build.gradle @@ -33,7 +33,7 @@ dependencies { // Add SDK via module. compileOnly project(':sdk-core') // Add SDK via Maven. - // implementation 'com.adjust.sdk:adjust-android:5.4.6' + // implementation 'com.adjust.sdk:adjust-android:5.5.0' implementation 'com.huawei.hms:ads-identifier:3.4.56.300' } diff --git a/Adjust/plugins/sdk-plugin-samsung-clouddev/build.gradle b/Adjust/plugins/sdk-plugin-samsung-clouddev/build.gradle index dc31bc5da..9af3bfc65 100644 --- a/Adjust/plugins/sdk-plugin-samsung-clouddev/build.gradle +++ b/Adjust/plugins/sdk-plugin-samsung-clouddev/build.gradle @@ -27,7 +27,7 @@ dependencies { // Add SDK via module. compileOnly project(':sdk-core') // Add SDK via Maven. - // implementation 'com.adjust.sdk:adjust-android:5.4.6' + // implementation 'com.adjust.sdk:adjust-android:5.5.0' // Add Samsung clouddev lib. compileOnly fileTree(include: ['*.aar'], dir: 'libs') diff --git a/Adjust/plugins/sdk-plugin-samsung-referrer/build.gradle b/Adjust/plugins/sdk-plugin-samsung-referrer/build.gradle index 9bc5fff8a..a66f841fc 100644 --- a/Adjust/plugins/sdk-plugin-samsung-referrer/build.gradle +++ b/Adjust/plugins/sdk-plugin-samsung-referrer/build.gradle @@ -27,7 +27,7 @@ dependencies { // Add SDK via module. compileOnly project(':sdk-core') // Add SDK via Maven. - // implementation 'com.adjust.sdk:adjust-android:5.4.6' + // implementation 'com.adjust.sdk:adjust-android:5.5.0' // Add Samsung referrer lib via Maven. implementation 'store.galaxy.samsung.installreferrer:samsung_galaxystore_install_referrer:3.0.1' diff --git a/Adjust/plugins/sdk-plugin-vivo-referrer/build.gradle b/Adjust/plugins/sdk-plugin-vivo-referrer/build.gradle index d8b77c964..3074c9ddf 100644 --- a/Adjust/plugins/sdk-plugin-vivo-referrer/build.gradle +++ b/Adjust/plugins/sdk-plugin-vivo-referrer/build.gradle @@ -30,7 +30,7 @@ dependencies { // Add SDK via module. compileOnly project(':sdk-core') // Add SDK via Maven. - // implementation 'com.adjust.sdk:adjust-android:5.4.6' + // implementation 'com.adjust.sdk:adjust-android:5.5.0' } // read local properties diff --git a/Adjust/plugins/sdk-plugin-webbridge/build.gradle b/Adjust/plugins/sdk-plugin-webbridge/build.gradle index aef9f8bb9..ff7422738 100644 --- a/Adjust/plugins/sdk-plugin-webbridge/build.gradle +++ b/Adjust/plugins/sdk-plugin-webbridge/build.gradle @@ -30,7 +30,7 @@ dependencies { // Add SDK via module. compileOnly project(':sdk-core') // Add SDK via Maven. - // implementation 'com.adjust.sdk:adjust-android:5.4.6' + // implementation 'com.adjust.sdk:adjust-android:5.5.0' } // read local properties diff --git a/Adjust/plugins/sdk-plugin-webbridge/src/main/assets/adjust.js b/Adjust/plugins/sdk-plugin-webbridge/src/main/assets/adjust.js index 98e42989e..d6cb9d206 100644 --- a/Adjust/plugins/sdk-plugin-webbridge/src/main/assets/adjust.js +++ b/Adjust/plugins/sdk-plugin-webbridge/src/main/assets/adjust.js @@ -1,4 +1,48 @@ +// generate random callback ID with prefix (similar to iOS implementation) +window.randomCallbackIdWithPrefix = function(prefix) { + const randomString = (Math.random() + 1).toString(36).substring(7); + return prefix + "_" + randomString; +}; + var Adjust = { + // map to store callbacks by their unique callback ID + _callbackMap: {}, + + _handleGetterCallback: function(callback, callbackId) { + // store callback in map + this._callbackMap[callbackId] = callback; + + // create a function on window with the callbackId name + // this function will be called by the native side + window[callbackId] = (function(adjustInstance, storedCallbackId) { + return function(value) { + var callback = adjustInstance._callbackMap[storedCallbackId]; + if (callback) { + // for attribution, the native side passes a JSON object that's already parsed + // for other values, they're passed as strings + if (storedCallbackId.includes("adjust_getAttribution")) { + // value is already a JavaScript object (parsed from JSON) + // only parse if it's actually a string (shouldn't happen) + if (typeof value === 'string') { + callback(JSON.parse(value)); + } else { + callback(value); + } + } else { + callback(value); + } + // clean up: remove callback and delete the function + delete adjustInstance._callbackMap[storedCallbackId]; + delete window[storedCallbackId]; + } else { + // callback was already cleaned up (teardown was called) + // safely remove the window function to prevent memory leaks + delete window[storedCallbackId]; + } + }; + })(this, callbackId); + }, + initSdk: function (adjustConfig) { if (adjustConfig && !adjustConfig.getSdkPrefix()) { adjustConfig.setSdkPrefix(this.getSdkPrefix()); @@ -49,26 +93,15 @@ var Adjust = { } // supports legacy return with callback if (arguments.length === 1) { - // with manual string call - if (typeof callback === 'string' || callback instanceof String) { - this.isEnabledCallbackName = callback; - } else { - // or save callback and call later - this.isEnabledCallbackName = 'Adjust.adjust_isEnabledCallback'; - this.isEnabledCallbackFunction = callback; - } - AdjustBridge.isEnabled(this.isEnabledCallbackName); + // generate unique callback ID + const callbackId = window.randomCallbackIdWithPrefix("adjust_isEnabled"); + this._handleGetterCallback(callback, callbackId); + AdjustBridge.isEnabled(callbackId); } else { return AdjustBridge.isEnabled(); } }, - adjust_isEnabledCallback: function (isEnabled) { - if (AdjustBridge && this.isEnabledCallbackFunction) { - this.isEnabledCallbackFunction(isEnabled); - } - }, - setReferrer: function (referrer) { if (AdjustBridge) { AdjustBridge.setReferrer(referrer); @@ -195,99 +228,62 @@ var Adjust = { getGoogleAdId: function (callback) { if (AdjustBridge) { - if (typeof callback === 'string' || callback instanceof String) { - this.getGoogleAdIdCallbackName = callback; - } else { - this.getGoogleAdIdCallbackName = 'Adjust.adjust_getGoogleAdIdCallback'; - this.getGoogleAdIdCallbackFunction = callback; - } - AdjustBridge.getGoogleAdId(this.getGoogleAdIdCallbackName); - } - }, - - adjust_getGoogleAdIdCallback: function (googleAdId) { - if (AdjustBridge && this.getGoogleAdIdCallbackFunction) { - this.getGoogleAdIdCallbackFunction(googleAdId); - } - }, - - getAmazonAdId: function (callback) { - if (AdjustBridge) { - return AdjustBridge.getAmazonAdId(); - } else { - return undefined; + const callbackId = window.randomCallbackIdWithPrefix("adjust_getGoogleAdId"); + this._handleGetterCallback(callback, callbackId); + AdjustBridge.getGoogleAdId(callbackId); } }, getAdid: function (callback) { - if (AdjustBridge) { - if (typeof callback === 'string' || callback instanceof String) { - this.getAdIdCallbackName = callback; - } else { - this.getAdIdCallbackName = 'Adjust.adjust_getAdIdCallback'; - this.getAdIdCallbackFunction = callback; - } - AdjustBridge.getAdid(this.getAdIdCallbackName); + if (AdjustBridge) { + const callbackId = window.randomCallbackIdWithPrefix("adjust_getAdid"); + this._handleGetterCallback(callback, callbackId); + AdjustBridge.getAdid(callbackId); } }, - adjust_getAdIdCallback: function (adId) { - if (AdjustBridge && this.getAdIdCallbackFunction) { - this.getAdIdCallbackFunction(adId); + getAdidWithTimeout: function (timeoutInMilliSec, callback) { + if (AdjustBridge) { + const callbackId = window.randomCallbackIdWithPrefix("adjust_getAdidWithTimeout"); + this._handleGetterCallback(callback, callbackId); + AdjustBridge.getAdidWithTimeout(timeoutInMilliSec, callbackId); } }, - getAmazonAdId: function (callbackSuccess,callbackFail) { - if (AdjustBridge) { - if (typeof callbackSuccess === 'string' || callbackSuccess instanceof String) { - this.getAmazonIdCallbackSuccessName = callbackSuccess; - } else { - this.getAmazonIdCallbackSuccessName = 'Adjust.adjust_getAmazonIdCallbackSuccess'; - this.getAmazonIdCallbackSuccessFunction = callbackSuccess; - } - AdjustBridge.getAmazonAdId(this.getAmazonIdCallbackSuccessName); - } - }, - - adjust_getAmazonIdCallbackSuccess: function (amazonId) { - if (AdjustBridge && this.getAmazonIdCallbackSuccessFunction) { - this.getAmazonIdCallbackSuccessFunction(amazonId); + getAmazonAdId: function (callbackSuccess, callbackFail) { + if (AdjustBridge) { + const callbackId = window.randomCallbackIdWithPrefix("adjust_getAmazonAdId"); + this._handleGetterCallback(callbackSuccess, callbackId); + // note: Java side only supports success callback, callbackFail is ignored + AdjustBridge.getAmazonAdId(callbackId); } }, getAttribution: function (callback) { - if (AdjustBridge) { - if (typeof callback === 'string' || callback instanceof String) { - this.getAttributionCallbackName = callback; - } else { - this.getAttributionCallbackName = 'Adjust.adjust_getAttributionCallback'; - this.getAttributionCallbackFunction = callback; - } - AdjustBridge.getAttribution(this.getAttributionCallbackName); + if (AdjustBridge) { + const callbackId = window.randomCallbackIdWithPrefix("adjust_getAttribution"); + this._handleGetterCallback(callback, callbackId); + AdjustBridge.getAttribution(callbackId); } }, - adjust_getAttributionCallback: function (attribution) { - if (AdjustBridge && this.getAttributionCallbackFunction) { - this.getAttributionCallbackFunction(attribution); + getAttributionWithTimeout: function (timeoutInMilliSec, callback) { + if (AdjustBridge) { + const callbackId = window.randomCallbackIdWithPrefix("adjust_getAttributionWithTimeout"); + this._handleGetterCallback(callback, callbackId); + AdjustBridge.getAttributionWithTimeout(timeoutInMilliSec, callbackId); } }, getSdkVersion: function (callback) { if (AdjustBridge) { - if (typeof callback === 'string' || callback instanceof String) { - this.getSdkVersionCallbackName = callback; - } else { - this.getSdkVersionCallbackName = 'Adjust.adjust_getSdkVersionCallback'; - this.getSdkVersionCallbackFunction = callback; - } - AdjustBridge.getSdkVersion(this.getSdkVersionCallbackName); - } - }, - - adjust_getSdkVersionCallback: function (sdkVersion) { - if (AdjustBridge && this.getSdkVersionCallbackFunction) { - this.getSdkVersionCallbackFunction(this.getSdkPrefix() + '@' + sdkVersion); + const callbackId = window.randomCallbackIdWithPrefix("adjust_getSdkVersion"); + // wrap callback to add SDK prefix before passing to _handleGetterCallback + const wrappedCallback = function(sdkVersion) { + callback(Adjust.getSdkPrefix() + '@' + sdkVersion); + }; + this._handleGetterCallback(wrappedCallback, callbackId); + AdjustBridge.getSdkVersion(callbackId); } }, @@ -295,7 +291,7 @@ var Adjust = { if (this.adjustConfig) { return this.adjustConfig.getSdkPrefix(); } else { - return 'web-bridge5.4.6'; + return 'web-bridge5.5.0'; } }, @@ -304,17 +300,6 @@ var Adjust = { AdjustBridge.teardown(); } this.adjustConfig = undefined; - this.isEnabledCallbackName = undefined; - this.isEnabledCallbackFunction = undefined; - this.getGoogleAdIdCallbackName = undefined; - this.getGoogleAdIdCallbackFunction = undefined; - this.getAdIdCallbackName = undefined; - this.getAdIdCallbackFunction = undefined; - this.getAttributionCallbackName = undefined; - this.getAttributionCallbackFunction = undefined; - this.getAmazonIdCallbackSuccessName = undefined; - this.getAmazonIdCallbackSuccessFunction = undefined; - this.getSdkVersionCallbackFunction = undefined; - this.getSdkVersionCallbackName = undefined; + this._callbackMap = {}; }, }; diff --git a/Adjust/plugins/sdk-plugin-webbridge/src/main/assets/adjust_config.js b/Adjust/plugins/sdk-plugin-webbridge/src/main/assets/adjust_config.js index c44b494d6..4550816cd 100644 --- a/Adjust/plugins/sdk-plugin-webbridge/src/main/assets/adjust_config.js +++ b/Adjust/plugins/sdk-plugin-webbridge/src/main/assets/adjust_config.js @@ -53,6 +53,7 @@ function AdjustConfig(appToken, environment, legacy) { this.eventDeduplicationIdsMaxSize = null; this.isFirstSessionDelayEnabled = null; this.storeInfo = null; + this.isAppSetIdReadingEnabled = null; } AdjustConfig.EnvironmentSandbox = 'sandbox'; @@ -248,3 +249,7 @@ AdjustConfig.prototype.enableFirstSessionDelay = function() { AdjustConfig.prototype.setStoreInfo = function(storeInfo) { this.storeInfo = JSON.stringify(storeInfo); }; + +AdjustConfig.prototype.disableAppSetIdReading = function() { + this.isAppSetIdReadingEnabled = false; +}; diff --git a/Adjust/plugins/sdk-plugin-webbridge/src/main/java/com/adjust/sdk/webbridge/AdjustBridgeInstance.java b/Adjust/plugins/sdk-plugin-webbridge/src/main/java/com/adjust/sdk/webbridge/AdjustBridgeInstance.java index 9fee832cd..236cd85d9 100644 --- a/Adjust/plugins/sdk-plugin-webbridge/src/main/java/com/adjust/sdk/webbridge/AdjustBridgeInstance.java +++ b/Adjust/plugins/sdk-plugin-webbridge/src/main/java/com/adjust/sdk/webbridge/AdjustBridgeInstance.java @@ -145,6 +145,7 @@ public void initSdk(String adjustConfigString) { Object eventDeduplicationIdsMaxSizeField = jsonAdjustConfig.get("eventDeduplicationIdsMaxSize"); Object isFirstSessionDelayEnabledField = jsonAdjustConfig.get("isFirstSessionDelayEnabled"); Object storeInfoField = jsonAdjustConfig.get("storeInfo"); + Object isAppSetIdReadingEnabledField = jsonAdjustConfig.get("isAppSetIdReadingEnabled"); String appToken = AdjustBridgeUtil.fieldToString(appTokenField); String environment = AdjustBridgeUtil.fieldToString(environmentField); @@ -398,6 +399,14 @@ public boolean launchReceivedDeeplink(Uri deeplink) { AdjustFactory.getLogger().error("AdjustBridgeInstance storeInfo: %s", e.getMessage()); } + // AppSetId reading + Boolean isAppSetIdReadingEnabled = AdjustBridgeUtil.fieldToBoolean(isAppSetIdReadingEnabledField); + if (isAppSetIdReadingEnabled != null) { + if (!isAppSetIdReadingEnabled) { + adjustConfig.disableAppSetIdReading(); + } + } + Adjust.initSdk(adjustConfig); isInitialized = true; @@ -756,6 +765,20 @@ public void onAdidRead(String adid) { }); } + @JavascriptInterface + public void getAdidWithTimeout(final long timeoutInMilliSec, final String callback) { + if (!isInitialized()) { + return; + } + Adjust.getAdidWithTimeout(application.getApplicationContext(), timeoutInMilliSec, + new OnAdidReadListener() { + @Override + public void onAdidRead(String adid) { + AdjustBridgeUtil.execAdidCallbackCommand(webView, callback, adid); + } + }); + } + @JavascriptInterface public void getAttribution(final String callback) { if (!isInitialized()) { @@ -770,6 +793,20 @@ public void onAttributionRead(AdjustAttribution attribution) { }); } + @JavascriptInterface + public void getAttributionWithTimeout(final long timeoutInMilliSec, final String callback) { + if (!isInitialized()) { + return; + } + Adjust.getAttributionWithTimeout(application.getApplicationContext(), timeoutInMilliSec, + new OnAttributionReadListener() { + @Override + public void onAttributionRead(AdjustAttribution attribution) { + AdjustBridgeUtil.execAttributionCallbackCommand(webView, callback, attribution); + } + }); + } + @JavascriptInterface public void getSdkVersion(final String callback) { if (!isInitialized()) { diff --git a/Adjust/plugins/sdk-plugin-webbridge/src/main/java/com/adjust/sdk/webbridge/AdjustBridgeUtil.java b/Adjust/plugins/sdk-plugin-webbridge/src/main/java/com/adjust/sdk/webbridge/AdjustBridgeUtil.java index ea3c66654..c3b7f8aee 100644 --- a/Adjust/plugins/sdk-plugin-webbridge/src/main/java/com/adjust/sdk/webbridge/AdjustBridgeUtil.java +++ b/Adjust/plugins/sdk-plugin-webbridge/src/main/java/com/adjust/sdk/webbridge/AdjustBridgeUtil.java @@ -116,35 +116,54 @@ public static Integer fieldToInteger(Object field) { } } - - public static void execAttributionCallbackCommand(final WebView webView, final String commandName, final AdjustAttribution attribution) { + public static void execAdidCallbackCommand(final WebView webView, final String commandName, final String adid) { if (webView == null) { return; } - if (attribution == null) { + + webView.post(new Runnable() { + @Override + public void run() { + if (adid != null ) { + String command = "javascript:" + commandName + "('" + adid + "');"; + webView.loadUrl(command); + } else { + String command = "javascript:" + commandName + "(null);"; + webView.loadUrl(command); + } + } + }); + } + + public static void execAttributionCallbackCommand(final WebView webView, final String commandName, final AdjustAttribution attribution) { + if (webView == null) { return; } - webView.post(new Runnable() { @Override public void run() { - JSONObject jsonAttribution = new JSONObject(); try { - jsonAttribution.put("trackerName", attribution.trackerName == null ? JSONObject.NULL : attribution.trackerName); - jsonAttribution.put("trackerToken", attribution.trackerToken == null ? JSONObject.NULL : attribution.trackerToken); - jsonAttribution.put("campaign", attribution.campaign == null ? JSONObject.NULL : attribution.campaign); - jsonAttribution.put("network", attribution.network == null ? JSONObject.NULL : attribution.network); - jsonAttribution.put("creative", attribution.creative == null ? JSONObject.NULL : attribution.creative); - jsonAttribution.put("adgroup", attribution.adgroup == null ? JSONObject.NULL : attribution.adgroup); - jsonAttribution.put("clickLabel", attribution.clickLabel == null ? JSONObject.NULL : attribution.clickLabel); - jsonAttribution.put("costType", attribution.costType == null ? JSONObject.NULL : attribution.costType); - jsonAttribution.put("costAmount", attribution.costAmount == null || attribution.costAmount.isNaN() ? 0 : attribution.costAmount); - jsonAttribution.put("costCurrency", attribution.costCurrency == null ? JSONObject.NULL : attribution.costCurrency); - jsonAttribution.put("fbInstallReferrer", attribution.fbInstallReferrer == null ? JSONObject.NULL : attribution.fbInstallReferrer); - jsonAttribution.put("jsonResponse", attribution.jsonResponse == null ? JSONObject.NULL : new JSONObject(attribution.jsonResponse)); - - String command = "javascript:" + commandName + "(" + jsonAttribution.toString() + ");"; - webView.loadUrl(command); + if (attribution != null) { + JSONObject jsonAttribution = new JSONObject(); + jsonAttribution.put("trackerName", attribution.trackerName == null ? JSONObject.NULL : attribution.trackerName); + jsonAttribution.put("trackerToken", attribution.trackerToken == null ? JSONObject.NULL : attribution.trackerToken); + jsonAttribution.put("campaign", attribution.campaign == null ? JSONObject.NULL : attribution.campaign); + jsonAttribution.put("network", attribution.network == null ? JSONObject.NULL : attribution.network); + jsonAttribution.put("creative", attribution.creative == null ? JSONObject.NULL : attribution.creative); + jsonAttribution.put("adgroup", attribution.adgroup == null ? JSONObject.NULL : attribution.adgroup); + jsonAttribution.put("clickLabel", attribution.clickLabel == null ? JSONObject.NULL : attribution.clickLabel); + jsonAttribution.put("costType", attribution.costType == null ? JSONObject.NULL : attribution.costType); + jsonAttribution.put("costAmount", attribution.costAmount == null || attribution.costAmount.isNaN() ? 0 : attribution.costAmount); + jsonAttribution.put("costCurrency", attribution.costCurrency == null ? JSONObject.NULL : attribution.costCurrency); + jsonAttribution.put("fbInstallReferrer", attribution.fbInstallReferrer == null ? JSONObject.NULL : attribution.fbInstallReferrer); + jsonAttribution.put("jsonResponse", attribution.jsonResponse == null ? JSONObject.NULL : new JSONObject(attribution.jsonResponse)); + + String command = "javascript:" + commandName + "(" + jsonAttribution.toString() + ");"; + webView.loadUrl(command); + } else { + String command = "javascript:" + commandName + "(null);"; + webView.loadUrl(command); + } } catch (Exception e) { e.printStackTrace(); } diff --git a/Adjust/plugins/sdk-plugin-xiaomi-referrer/build.gradle b/Adjust/plugins/sdk-plugin-xiaomi-referrer/build.gradle index 0290bdc30..b240e6eba 100644 --- a/Adjust/plugins/sdk-plugin-xiaomi-referrer/build.gradle +++ b/Adjust/plugins/sdk-plugin-xiaomi-referrer/build.gradle @@ -31,7 +31,7 @@ dependencies { // Add SDK via module. compileOnly project(':sdk-core') // Add SDK via Maven. - // implementation 'com.adjust.sdk:adjust-android:5.4.6' + // implementation 'com.adjust.sdk:adjust-android:5.5.0' // Add xiaomi referrer lib via Maven. implementation 'com.miui.referrer:homereferrer:1.0.0.6' diff --git a/Adjust/sdk-core/build.gradle b/Adjust/sdk-core/build.gradle index 4a0ba57f7..40ec1ddd1 100644 --- a/Adjust/sdk-core/build.gradle +++ b/Adjust/sdk-core/build.gradle @@ -220,7 +220,7 @@ def customizePomForAar(pom) { dependency { groupId 'com.adjust.signature' artifactId 'adjust-android-signature' - version '3.61.0' + version '3.62.0' scope 'compile' } } diff --git a/Adjust/sdk-core/src/main/java/com/adjust/sdk/ActivityHandler.java b/Adjust/sdk-core/src/main/java/com/adjust/sdk/ActivityHandler.java index 396c43981..18d795380 100644 --- a/Adjust/sdk-core/src/main/java/com/adjust/sdk/ActivityHandler.java +++ b/Adjust/sdk-core/src/main/java/com/adjust/sdk/ActivityHandler.java @@ -10,9 +10,12 @@ package com.adjust.sdk; import static com.adjust.sdk.Constants.ACTIVITY_STATE_FILENAME; +import static com.adjust.sdk.Constants.ADID_TIMEOUT_TIMER_NAME; import static com.adjust.sdk.Constants.ATTRIBUTION_FILENAME; +import static com.adjust.sdk.Constants.ATTRIBUTION_TIMEOUT_TIMER_NAME; import static com.adjust.sdk.Constants.CONNECTION_TIMEOUT; import static com.adjust.sdk.Constants.CONNECTION_TIMEOUT_VERIFY; +import static com.adjust.sdk.Constants.EVENT_METADATA_FILENAME; import static com.adjust.sdk.Constants.GLOBAL_CALLBACK_PARAMETERS_FILENAME; import static com.adjust.sdk.Constants.GLOBAL_PARTNER_PARAMETERS_FILENAME; import static com.adjust.sdk.Constants.REFERRER_API_HUAWEI_ADS; @@ -68,6 +71,7 @@ public class ActivityHandler private static final String GLOBAL_CALLBACK_PARAMETERS_NAME = "Global Callback parameters"; private static final String GLOBAL_PARTNER_PARAMETERS_NAME = "Global Partner parameters"; private static final String GLOBAL_PARAMETERS_NAME = "Global parameters"; + private static final String EVENT_METADATA_NAME = "Event metadata"; ThreadExecutor executor; private IPackageHandler packageHandler; @@ -89,12 +93,15 @@ public class ActivityHandler private GlobalParameters globalParameters; private InstallReferrer installReferrer; private OnDeeplinkResolvedListener cachedDeeplinkResolutionCallback; - private ArrayList cachedAdidReadCallbacks = new ArrayList<>(); + private final ArrayList cachedAdidReadCallbacks = new ArrayList<>(); + private final ArrayList cachedAdidReadTimeoutCallbacks = new ArrayList<>(); private SystemLifecycle systemLifecycle; - private ArrayList cachedAttributionReadCallbacks = new ArrayList<>(); + private final ArrayList cachedAttributionReadCallbacks = new ArrayList<>(); + private final ArrayList cachedAttributionReadTimeoutCallbacks = new ArrayList<>(); private List cachedAdjustThirdPartySharingArray; private Boolean cachedLastMeasurementConsentTrack; private FirstSessionDelayManager firstSessionDelayManager; + private @NonNull EventMetadata eventMetadata = new EventMetadata(); @Override public void teardown() { @@ -131,6 +138,7 @@ public void teardown() { teardownActivityStateS(); teardownAttributionS(); teardownAllGlobalParametersS(); + teardownEventMetadataS(); packageHandler = null; logger = null; @@ -151,6 +159,7 @@ static void deleteState(Context context) { deleteAttribution(context); deleteGlobalCallbackParameters(context); deleteGlobalPartnerParameters(context); + deleteEventMetadata(context); SharedPreferencesManager.getDefaultInstance(context).clear(); } @@ -261,6 +270,7 @@ private ActivityHandler(AdjustConfig adjustConfig) { // has to be read in the background readAttributionI(adjustConfig.context); readActivityStateI(adjustConfig.context); + readEventMetadataI(adjustConfig.context); firstSessionDelayManager.activityStateFileReadI(); }); @@ -465,15 +475,21 @@ private void updateAdidI(final String adid) { writeActivityStateI(); } - if (! cachedAdidReadCallbacks.isEmpty()) { - final ArrayList cachedAdidReadCallbacksCopy = - new ArrayList<>(cachedAdidReadCallbacks); + ArrayList cachedAdidReadCallbacksCopy = null; + synchronized (cachedAdidReadCallbacks) { + if (!cachedAdidReadCallbacks.isEmpty()) { + cachedAdidReadCallbacksCopy = new ArrayList<>(cachedAdidReadCallbacks); + cachedAdidReadCallbacks.clear(); + } + } - cachedAdidReadCallbacks.clear(); + // process regular adid callbacks + if (cachedAdidReadCallbacksCopy != null) { + ArrayList finalCachedAdidReadCallbacksCopy = cachedAdidReadCallbacksCopy; new Handler(adjustConfig.context.getMainLooper()).post(new Runnable() { @Override public void run() { - for (OnAdidReadListener onAdidReadListener : cachedAdidReadCallbacksCopy) { + for (OnAdidReadListener onAdidReadListener : finalCachedAdidReadCallbacksCopy) { if (onAdidReadListener != null) { onAdidReadListener.onAdidRead(adid); } @@ -481,6 +497,41 @@ public void run() { } }); } + + ArrayList cachedAdidReadTimeoutCallbacksCopy = null; + synchronized (cachedAdidReadTimeoutCallbacks) { + if (!cachedAdidReadTimeoutCallbacks.isEmpty()) { + cachedAdidReadTimeoutCallbacksCopy = new ArrayList<>(cachedAdidReadTimeoutCallbacks); + cachedAdidReadTimeoutCallbacks.clear(); + } + } + + // process timeout adid callbacks + if (cachedAdidReadTimeoutCallbacksCopy != null) { + ArrayList finalCachedAdidReadTimeoutCallbacksCopy = cachedAdidReadTimeoutCallbacksCopy; + new Handler(adjustConfig.context.getMainLooper()).post(new Runnable() { + @Override + public void run() { + for (AdjustTimeoutCallback adjustTimeoutCallback : finalCachedAdidReadTimeoutCallbacksCopy) { + if (adjustTimeoutCallback != null) { + // cancel any pending timeout + TimerOnce timerOnce = adjustTimeoutCallback.getTimeoutTimer(); + if (timerOnce != null) { + timerOnce.cancel(); + } + + OnAdidReadListener onAdidReadListener = adjustTimeoutCallback.getOnAdidReadListener(); + if (onAdidReadListener != null) { + onAdidReadListener.onAdidRead(adid); + } + + // null callback to call it only once + adjustTimeoutCallback.setOnAdidReadListener(null); + } + } + } + }); + } } @Override @@ -493,15 +544,21 @@ public boolean updateAttributionI(AdjustAttribution attribution) { return false; } - if (! cachedAttributionReadCallbacks.isEmpty()) { - final ArrayList cachedAttributionReadCallbacksCopy = - new ArrayList<>(cachedAttributionReadCallbacks); + ArrayList cachedAttributionReadCallbacksCopy = null; + synchronized (cachedAttributionReadCallbacks) { + if (! cachedAttributionReadCallbacks.isEmpty()) { + cachedAttributionReadCallbacksCopy = new ArrayList<>(cachedAttributionReadCallbacks); + cachedAttributionReadCallbacks.clear(); + } + } - cachedAttributionReadCallbacks.clear(); + // process regular attribution callbacks + if (cachedAttributionReadCallbacksCopy != null) { + ArrayList finalCachedAttributionReadCallbacksCopy = cachedAttributionReadCallbacksCopy; new Handler(adjustConfig.context.getMainLooper()).post(new Runnable() { @Override public void run() { - for (OnAttributionReadListener onAttributionReadListener : cachedAttributionReadCallbacksCopy) { + for (OnAttributionReadListener onAttributionReadListener : finalCachedAttributionReadCallbacksCopy) { if (onAttributionReadListener != null) { onAttributionReadListener.onAttributionRead(attribution); } @@ -510,6 +567,39 @@ public void run() { }); } + ArrayList cachedAttributionReadTimeoutCallbacksCopy = null; + synchronized (cachedAttributionReadTimeoutCallbacks) { + if (! cachedAttributionReadTimeoutCallbacks.isEmpty()) { + cachedAttributionReadTimeoutCallbacksCopy = new ArrayList<>(cachedAttributionReadTimeoutCallbacks); + cachedAttributionReadTimeoutCallbacks.clear(); + } + } + + // process timeout attribution callbacks + if (cachedAttributionReadTimeoutCallbacksCopy != null) { + ArrayList finalCachedAttributionReadTimeoutCallbacksCopy = cachedAttributionReadTimeoutCallbacksCopy; + new Handler(adjustConfig.context.getMainLooper()).post(new Runnable() { + @Override + public void run() { + for (AdjustTimeoutCallback adjustTimeoutCallback : finalCachedAttributionReadTimeoutCallbacksCopy) { + if (adjustTimeoutCallback != null) { + // cancel any pending timeout + TimerOnce timerOnce = adjustTimeoutCallback.getTimeoutTimer(); + if (timerOnce != null) { + timerOnce.cancel(); + } + OnAttributionReadListener onAttributionReadListener = adjustTimeoutCallback.getOnAttributionReadListener(); + if (onAttributionReadListener != null) { + onAttributionReadListener.onAttributionRead(attribution); + } + // null callback to call it only once + adjustTimeoutCallback.setOnAttributionReadListener(null); + } + } + } + }); + } + if (attribution.equals(this.attribution)) { return false; } @@ -767,7 +857,29 @@ public void run() { if (activityState == null) { logger.warn("SDK needs to be initialized before getting adid"); } - cachedAdidReadCallbacks.add(callback); + synchronized (cachedAdidReadCallbacks) { + cachedAdidReadCallbacks.add(callback); + } + } + } + + @Override + public void getAdidWithTimeout(long timeoutInMilliSec, OnAdidReadListener callback) { + if (activityState != null && activityState.adid != null) { + new Handler(adjustConfig.context.getMainLooper()).post(new Runnable() { + @Override + public void run() { + callback.onAdidRead(activityState.adid); + } + }); + } else { + if (activityState == null) { + logger.warn("SDK needs to be initialized before getting adid"); + } + + // adid not found in activity state + // queue timeout callback for later processing + queueGetAdidWithTimeout(timeoutInMilliSec, callback, cachedAdidReadTimeoutCallbacks, getContext()); } } @@ -781,7 +893,25 @@ public void run() { } }); }else { - cachedAttributionReadCallbacks.add(onAttributionReadListener); + synchronized (cachedAttributionReadCallbacks) { + cachedAttributionReadCallbacks.add(onAttributionReadListener); + } + } + } + + @Override + public void getAttributionWithTimeout(long timeoutInMilliSec, OnAttributionReadListener onAttributionReadListener) { + if (attribution != null) { + new Handler(adjustConfig.context.getMainLooper()).post(new Runnable() { + @Override + public void run() { + onAttributionReadListener.onAttributionRead(attribution); + } + }); + } else { + // attribution not found + // queue timeout callback for later processing + queueGetAttributionWithTimeout(timeoutInMilliSec, onAttributionReadListener, cachedAttributionReadTimeoutCallbacks, getContext()); } } @@ -826,6 +956,88 @@ public InternalState getInternalState() { return internalState; } + public static void queueGetAdidWithTimeout(final long timeoutInMilliSec, + final OnAdidReadListener onAdidReadListener, + final ArrayList cachedAdidReadTimeoutCallbacks, + final Context context) { + AdjustTimeoutCallback timeoutCallback = new AdjustTimeoutCallback(onAdidReadListener); + + // cache the callback before starting the timer + synchronized (cachedAdidReadTimeoutCallbacks) { + cachedAdidReadTimeoutCallbacks.add(timeoutCallback); + } + + // set up timeout timer immediately + TimerOnce timerOnce = new TimerOnce(new Runnable() { + @Override + public void run() { + if (timeoutCallback.getOnAdidReadListener() != null) { + // remove callback from array and call callback with null + synchronized (cachedAdidReadTimeoutCallbacks) { + cachedAdidReadTimeoutCallbacks.remove(timeoutCallback); + } + new Handler(context.getMainLooper()).post(new Runnable() { + @Override + public void run() { + // if timer elapses, return null (only if callback still exists) + OnAdidReadListener onAdidReadListener = timeoutCallback.getOnAdidReadListener(); + if (onAdidReadListener != null) { + onAdidReadListener.onAdidRead(null); + } + + // null callback to call it only once + timeoutCallback.setOnAdidReadListener(null); + } + }); + } + } + }, ADID_TIMEOUT_TIMER_NAME); + + timeoutCallback.setTimer(timerOnce); + timerOnce.startIn(timeoutInMilliSec); + } + + public static void queueGetAttributionWithTimeout(final long timeoutInMilliSec, + final OnAttributionReadListener attributionReadListener, + final ArrayList cachedAttributionReadTimeoutCallbacks, + final Context context) { + AdjustTimeoutCallback timeoutCallback = new AdjustTimeoutCallback(attributionReadListener); + + // cache the callback before starting the timer + synchronized (cachedAttributionReadTimeoutCallbacks) { + cachedAttributionReadTimeoutCallbacks.add(timeoutCallback); + } + + // set up timeout timer immediately + TimerOnce timerOnce = new TimerOnce(new Runnable() { + @Override + public void run() { + if (timeoutCallback.getOnAttributionReadListener() != null) { + // remove callback from array and call callback with null + synchronized (cachedAttributionReadTimeoutCallbacks) { + cachedAttributionReadTimeoutCallbacks.remove(timeoutCallback); + } + new Handler(context.getMainLooper()).post(new Runnable() { + @Override + public void run() { + // if timer elapses, return null (only if callback still exists) + OnAttributionReadListener onAttributionReadListener = timeoutCallback.getOnAttributionReadListener(); + if (onAttributionReadListener != null) { + onAttributionReadListener.onAttributionRead(null); + } + + // null callback to call it only once + timeoutCallback.setOnAttributionReadListener(null); + } + }); + } + } + }, ATTRIBUTION_TIMEOUT_TIMER_NAME); + + timeoutCallback.setTimer(timerOnce); + timerOnce.startIn(timeoutInMilliSec); + } + void initI() { SESSION_INTERVAL = AdjustFactory.getSessionInterval(); SUBSESSION_INTERVAL = AdjustFactory.getSubsessionInterval(); @@ -1028,21 +1240,36 @@ public void onFail(String message) { } private void handleAttributionCallbackI() { - cachedAttributionReadCallbacks.addAll(adjustConfig.cachedAttributionReadCallbacks); - adjustConfig.cachedAttributionReadCallbacks.clear(); + synchronized (cachedAttributionReadCallbacks) { + cachedAttributionReadCallbacks.addAll(adjustConfig.cachedAttributionReadCallbacks); + adjustConfig.cachedAttributionReadCallbacks.clear(); + } - if (! cachedAttributionReadCallbacks.isEmpty() - && attribution != null) - { - final ArrayList cachedAttributionReadCallbacksCopy = - new ArrayList<>(cachedAttributionReadCallbacks); - final AdjustAttribution attributionCopy = attribution; + synchronized (cachedAttributionReadTimeoutCallbacks) { + cachedAttributionReadTimeoutCallbacks.addAll(adjustConfig.cachedAttributionReadTimeoutCallbacks); + adjustConfig.cachedAttributionReadTimeoutCallbacks.clear(); + } + + if (attribution == null) { + return; + } + + ArrayList cachedAttributionReadCallbacksCopy = null; + synchronized (cachedAttributionReadCallbacks) { + if (! cachedAttributionReadCallbacks.isEmpty()) { + cachedAttributionReadCallbacksCopy = new ArrayList<>(cachedAttributionReadCallbacks); + cachedAttributionReadCallbacks.clear(); + } + } - cachedAttributionReadCallbacks.clear(); + // process regular attribution callbacks + if (cachedAttributionReadCallbacksCopy != null) { + final AdjustAttribution attributionCopy = attribution; + ArrayList finalCachedAttributionReadCallbacksCopy = cachedAttributionReadCallbacksCopy; new Handler(adjustConfig.context.getMainLooper()).post(new Runnable() { @Override public void run() { - for (OnAttributionReadListener onAttributionReadListener : cachedAttributionReadCallbacksCopy) { + for (OnAttributionReadListener onAttributionReadListener : finalCachedAttributionReadCallbacksCopy) { if (onAttributionReadListener != null) { onAttributionReadListener.onAttributionRead(attributionCopy); } @@ -1050,25 +1277,74 @@ public void run() { } }); } + + ArrayList cachedAttributionReadTimeoutCallbacksCopy = null; + synchronized (cachedAttributionReadTimeoutCallbacks) { + if (! cachedAttributionReadTimeoutCallbacks.isEmpty()) { + cachedAttributionReadTimeoutCallbacksCopy = new ArrayList<>(cachedAttributionReadTimeoutCallbacks); + cachedAttributionReadTimeoutCallbacks.clear(); + } + } + + // process timeout attribution callbacks + if (cachedAttributionReadTimeoutCallbacksCopy != null) { + final AdjustAttribution attributionCopy = attribution; + ArrayList finalCachedAttributionReadTimeoutCallbacksCopy = cachedAttributionReadTimeoutCallbacksCopy; + new Handler(adjustConfig.context.getMainLooper()).post(new Runnable() { + @Override + public void run() { + for (AdjustTimeoutCallback adjustTimeoutCallback : finalCachedAttributionReadTimeoutCallbacksCopy) { + if (adjustTimeoutCallback != null) { + // cancel any pending timeout + TimerOnce timerOnce = adjustTimeoutCallback.getTimeoutTimer(); + if (timerOnce != null) { + timerOnce.cancel(); + } + OnAttributionReadListener onAttributionReadListener = adjustTimeoutCallback.getOnAttributionReadListener(); + if (onAttributionReadListener != null) { + onAttributionReadListener.onAttributionRead(attributionCopy); + } + + // null callback to call it only once + adjustTimeoutCallback.setOnAttributionReadListener(null); + } + } + } + }); + } } private void handleAdidCallbackI() { - cachedAdidReadCallbacks.addAll(adjustConfig.cachedAdidReadCallbacks); - adjustConfig.cachedAdidReadCallbacks.clear(); + synchronized (cachedAdidReadCallbacks) { + cachedAdidReadCallbacks.addAll(adjustConfig.cachedAdidReadCallbacks); + adjustConfig.cachedAdidReadCallbacks.clear(); + } - if (! cachedAdidReadCallbacks.isEmpty() - && activityState != null - && activityState.adid != null) - { - final ArrayList cachedAdidReadCallbacksCopy = - new ArrayList<>(cachedAdidReadCallbacks); - final String adidCopy = activityState.adid; + synchronized (cachedAdidReadTimeoutCallbacks) { + cachedAdidReadTimeoutCallbacks.addAll(adjustConfig.cachedAdidReadTimeoutCallbacks); + adjustConfig.cachedAdidReadTimeoutCallbacks.clear(); + } - cachedAdidReadCallbacks.clear(); + if (activityState == null || activityState.adid == null) { + return; + } + + ArrayList cachedAdidReadCallbacksCopy = null; + synchronized (cachedAdidReadCallbacks) { + if (! cachedAdidReadCallbacks.isEmpty()) { + cachedAdidReadCallbacksCopy = new ArrayList<>(cachedAdidReadCallbacks); + cachedAdidReadCallbacks.clear(); + } + } + + // process regular adid callbacks + if (cachedAdidReadCallbacksCopy != null) { + final String adidCopy = activityState.adid; + ArrayList finalCachedAdidReadCallbacksCopy = cachedAdidReadCallbacksCopy; new Handler(adjustConfig.context.getMainLooper()).post(new Runnable() { @Override public void run() { - for (OnAdidReadListener onAdidReadListener : cachedAdidReadCallbacksCopy) { + for (OnAdidReadListener onAdidReadListener : finalCachedAdidReadCallbacksCopy) { if (onAdidReadListener != null) { onAdidReadListener.onAdidRead(adidCopy); } @@ -1076,6 +1352,42 @@ public void run() { } }); } + + + ArrayList cachedAdidReadTimeoutCallbacksCopy = null; + synchronized (cachedAdidReadTimeoutCallbacks) { + if (! cachedAdidReadTimeoutCallbacks.isEmpty()) { + cachedAdidReadTimeoutCallbacksCopy = new ArrayList<>(cachedAdidReadTimeoutCallbacks); + cachedAdidReadTimeoutCallbacks.clear(); + } + } + + // process timeout adid callbacks + if (cachedAdidReadTimeoutCallbacksCopy != null) { + final String adidCopy = activityState.adid; + ArrayList finalCachedAdidReadTimeoutCallbacksCopy = cachedAdidReadTimeoutCallbacksCopy; + new Handler(adjustConfig.context.getMainLooper()).post(new Runnable() { + @Override + public void run() { + for (AdjustTimeoutCallback adjustTimeoutCallback : finalCachedAdidReadTimeoutCallbacksCopy) { + if (adjustTimeoutCallback != null) { + // cancel any pending timeout + TimerOnce timerOnce = adjustTimeoutCallback.getTimeoutTimer(); + if (timerOnce != null) { + timerOnce.cancel(); + } + OnAdidReadListener onAdidReadListener = adjustTimeoutCallback.getOnAdidReadListener(); + if (onAdidReadListener != null) { + onAdidReadListener.onAdidRead(adidCopy); + } + + // null callback to call it only once + adjustTimeoutCallback.setOnAdidReadListener(null); + } + } + } + }); + } } private void bootstrapLifecycleI() { @@ -1556,13 +1868,15 @@ private void trackEventI(AdjustEvent event) { long now = System.currentTimeMillis(); + int sequence = eventMetadata.incrementSequenceForEvent(event.eventToken); + activityState.eventCount++; updateActivityStateI(now); PackageBuilder eventBuilder = new PackageBuilder( adjustConfig, deviceInfo, activityState, globalParameters, firstSessionDelayManager, now); eventBuilder.internalState = internalState; - ActivityPackage eventPackage = eventBuilder.buildEventPackage(event); + ActivityPackage eventPackage = eventBuilder.buildEventPackage(event, sequence); packageHandler.addPackage(eventPackage); packageHandler.sendFirstPackage(); @@ -1573,6 +1887,7 @@ private void trackEventI(AdjustEvent event) { } writeActivityStateI(); + writeEventMetadataI(); } private void launchEventResponseTasksI(final EventResponseData eventResponseData) { @@ -2232,6 +2547,10 @@ public static boolean deleteAttribution(Context context) { return context.deleteFile(ATTRIBUTION_FILENAME); } + public static boolean deleteEventMetadata(Context context) { + return context.deleteFile(EVENT_METADATA_FILENAME); + } + public static boolean deleteGlobalCallbackParameters(Context context) { return context.deleteFile(GLOBAL_CALLBACK_PARAMETERS_FILENAME); } @@ -2738,6 +3057,19 @@ private void readAttributionI(Context context) { } } + private void readEventMetadataI(Context context) { + try { + eventMetadata = Util.readObject(context, EVENT_METADATA_FILENAME, EVENT_METADATA_NAME, EventMetadata.class); + } catch (Exception e) { + logger.error("Failed to read %s file (%s)", EVENT_METADATA_NAME, e.getMessage()); + } + + if (eventMetadata == null) { + eventMetadata = new EventMetadata(); + } + } + + @SuppressWarnings("unchecked") private void readGlobalCallbackParametersI(Context context) { try { @@ -2818,6 +3150,7 @@ private void writeGlobalPartnerParametersI() { } } + private void teardownAllGlobalParametersS() { synchronized (GlobalParameters.class) { if (globalParameters == null) { @@ -2827,6 +3160,24 @@ private void teardownAllGlobalParametersS() { } } + private void writeEventMetadataI() { + synchronized (EventMetadata.class) { + if (eventMetadata == null) { + return; + } + Util.writeObject(eventMetadata, adjustConfig.context, EVENT_METADATA_FILENAME, EVENT_METADATA_NAME); + } + } + + private void teardownEventMetadataS() { + synchronized (EventMetadata.class) { + if (eventMetadata == null) { + return; + } + eventMetadata = null; + } + } + private boolean checkEventI(AdjustEvent event) { if (event == null) { logger.error("Event missing"); diff --git a/Adjust/sdk-core/src/main/java/com/adjust/sdk/Adjust.java b/Adjust/sdk-core/src/main/java/com/adjust/sdk/Adjust.java index 4ad955b45..0f163966e 100644 --- a/Adjust/sdk-core/src/main/java/com/adjust/sdk/Adjust.java +++ b/Adjust/sdk-core/src/main/java/com/adjust/sdk/Adjust.java @@ -32,7 +32,7 @@ private Adjust() { */ public static synchronized AdjustInstance getDefaultInstance() { @SuppressWarnings("unused") - String VERSION = "!SDK-VERSION-STRING!:com.adjust.sdk:adjust-android:5.4.6"; + String VERSION = "!SDK-VERSION-STRING!:com.adjust.sdk:adjust-android:5.5.0"; if (defaultInstance == null) { defaultInstance = new AdjustInstance(); @@ -337,6 +337,33 @@ public static void getAdid(final OnAdidReadListener onAdidReadListener) { adjustInstance.getAdid(onAdidReadListener); } + /** + * Called to get value of unique Adjust device identifier with a timeout + * + * @param context Application context + * @param timeoutInMilliSec Timeout in milliseconds. If adid is not available within this time, + * the callback will return null adid. + * @param onAdidReadListener Callback to get triggered once identifier is obtained. + */ + public static void getAdidWithTimeout(final Context context, final long timeoutInMilliSec, + final OnAdidReadListener onAdidReadListener) { + if (context == null) { + AdjustFactory.getLogger().error("Context for getting adid can't be null"); + return; + } + if (timeoutInMilliSec < 0) { + AdjustFactory.getLogger().error("Timeout value for getting adid can't be negative"); + return; + } + if (onAdidReadListener == null) { + AdjustFactory.getLogger().error("Callback for getting adid can't be null"); + return; + } + + AdjustInstance adjustInstance = Adjust.getDefaultInstance(); + adjustInstance.getAdidWithTimeout(extractApplicationContext(context), timeoutInMilliSec, onAdidReadListener); + } + /** * Called to get user's current attribution value. * @@ -351,6 +378,33 @@ public static void getAttribution(final OnAttributionReadListener attributionRea adjustInstance.getAttribution(attributionReadListener); } + /** + * Called to get user's current attribution value. + * + * @param context Application context + * @param timeoutInMilliSec Timeout in milliseconds. If attribution is not available within this time, + * the callback will return null attribution. + * @param attributionReadListener Callback to get triggered once attribution is obtained + */ + public static void getAttributionWithTimeout(final Context context, final long timeoutInMilliSec, + final OnAttributionReadListener attributionReadListener) { + if (context == null) { + AdjustFactory.getLogger().error("Context for getting attribution can't be null"); + return; + } + if (timeoutInMilliSec < 0) { + AdjustFactory.getLogger().error("Timeout value for getting attribution can't be negative"); + return; + } + if (attributionReadListener == null) { + AdjustFactory.getLogger().error("Callback for getting attribution can't be null"); + return; + } + + AdjustInstance adjustInstance = Adjust.getDefaultInstance(); + adjustInstance.getAttributionWithTimeout(extractApplicationContext(context), timeoutInMilliSec, attributionReadListener); + } + /** * Called to get Google Install Referrer. * diff --git a/Adjust/sdk-core/src/main/java/com/adjust/sdk/AdjustConfig.java b/Adjust/sdk-core/src/main/java/com/adjust/sdk/AdjustConfig.java index a02e8875d..3858ce77f 100644 --- a/Adjust/sdk-core/src/main/java/com/adjust/sdk/AdjustConfig.java +++ b/Adjust/sdk-core/src/main/java/com/adjust/sdk/AdjustConfig.java @@ -44,10 +44,13 @@ public class AdjustConfig { boolean isDeviceIdsReadingOnceEnabled; OnDeeplinkResolvedListener cachedDeeplinkResolutionCallback; ArrayList cachedAdidReadCallbacks = new ArrayList<>(); + ArrayList cachedAdidReadTimeoutCallbacks = new ArrayList<>(); Integer eventDeduplicationIdsMaxSize; ArrayList cachedAttributionReadCallbacks = new ArrayList<>(); + ArrayList cachedAttributionReadTimeoutCallbacks = new ArrayList<>(); boolean isFirstSessionDelayEnabled; AdjustStoreInfo storeInfo; + boolean isAppSetIdReadingEnabled; public static final String ENVIRONMENT_SANDBOX = "sandbox"; public static final String ENVIRONMENT_PRODUCTION = "production"; @@ -86,6 +89,7 @@ private void init(Context context, String appToken, String environment, boolean this.coppaComplianceEnabled = false; this.playStoreKidsComplianceEnabled = false; this.isFirstSessionDelayEnabled = false; + this.isAppSetIdReadingEnabled = true; } public void setLogLevel(LogLevel logLevel) { @@ -190,6 +194,10 @@ public void setOnDeferredDeeplinkResponseListener(OnDeferredDeeplinkResponseList this.onDeferredDeeplinkResponseListener = onDeferredDeeplinkResponseListener; } + public void disableAppSetIdReading() { + this.isAppSetIdReadingEnabled = false; + } + public Context getContext() { return context; } @@ -290,6 +298,10 @@ public ILogger getLogger() { return logger; } + public boolean isAppSetIdReadingEnabled() { + return isAppSetIdReadingEnabled; + } + private boolean checkContext(Context context) { if (context == null) { logger.error("Missing context"); diff --git a/Adjust/sdk-core/src/main/java/com/adjust/sdk/AdjustInstance.java b/Adjust/sdk-core/src/main/java/com/adjust/sdk/AdjustInstance.java index f414b4d32..fe2d669c1 100644 --- a/Adjust/sdk-core/src/main/java/com/adjust/sdk/AdjustInstance.java +++ b/Adjust/sdk-core/src/main/java/com/adjust/sdk/AdjustInstance.java @@ -2,6 +2,7 @@ import android.net.Uri; import android.content.Context; +import android.os.Handler; import com.adjust.sdk.scheduler.AsyncTaskExecutor; import com.adjust.sdk.scheduler.SingleThreadCachedScheduler; @@ -49,8 +50,10 @@ public PreLaunchActions() { private OnDeeplinkResolvedListener cachedDeeplinkResolutionCallback; - private ArrayList cachedAdidReadCallbacks = new ArrayList<>(); - private ArrayList cachedAttributionReadCallbacks = new ArrayList<>(); + private final ArrayList cachedAdidReadCallbacks = new ArrayList<>(); + private final ArrayList cachedAdidReadTimeoutCallbacks = new ArrayList<>(); + private final ArrayList cachedAttributionReadCallbacks = new ArrayList<>(); + private final ArrayList cachedAttributionReadTimeoutCallbacks = new ArrayList<>(); /** * Base path for Adjust packages. */ @@ -104,7 +107,9 @@ public void initSdk(final AdjustConfig adjustConfig) { adjustConfig.purchaseVerificationPath = this.purchaseVerificationPath; adjustConfig.cachedDeeplinkResolutionCallback = cachedDeeplinkResolutionCallback; adjustConfig.cachedAdidReadCallbacks = cachedAdidReadCallbacks; + adjustConfig.cachedAdidReadTimeoutCallbacks = cachedAdidReadTimeoutCallbacks; adjustConfig.cachedAttributionReadCallbacks = cachedAttributionReadCallbacks; + adjustConfig.cachedAttributionReadTimeoutCallbacks = cachedAttributionReadTimeoutCallbacks; activityHandler = AdjustFactory.getActivityHandler(adjustConfig); setSendingReferrersAsNotSent(adjustConfig.context); @@ -523,6 +528,39 @@ public void getAdid(OnAdidReadListener onAdidReadListener) { activityHandler.getAdid(onAdidReadListener); } + /** + * Called to get value of unique Adjust device identifier. + * + * @param context Application context + * @param timeoutInMilliSec Timeout in milliseconds. + * @param onAdidReadListener Callback to get triggered once identifier is obtained. + */ + public void getAdidWithTimeout(Context context, long timeoutInMilliSec, OnAdidReadListener onAdidReadListener) { + if (!checkActivityHandler("getAdidWithTimeout")) { + ThreadExecutor executor = new SingleThreadCachedScheduler("getAdidWithTimeout"); + executor.submit(new Runnable() { + @Override + public void run() { + String adid = Util.getAdidFromActivityStateFile(context); + if (adid != null) { + new Handler(context.getMainLooper()).post(new Runnable() { + @Override + public void run() { + onAdidReadListener.onAdidRead(adid); + } + }); + return; + } + + // adid not found locally + ActivityHandler.queueGetAdidWithTimeout(timeoutInMilliSec, onAdidReadListener, cachedAdidReadTimeoutCallbacks, context); + } + }); + return; + } + activityHandler.getAdidWithTimeout(timeoutInMilliSec, onAdidReadListener); + } + /** * Called to get user's current attribution value. * @@ -536,6 +574,38 @@ public void getAttribution(OnAttributionReadListener attributionReadListener) { activityHandler.getAttribution(attributionReadListener); } + /** + * Called to get user's current attribution value. + * + * @param context Application context + * @param timeoutInMilliSec Timeout in milliseconds. + * @param attributionReadListener Callback to get triggered once attribution is obtained. + */ + public void getAttributionWithTimeout(Context context, long timeoutInMilliSec, OnAttributionReadListener attributionReadListener) { + if (!checkActivityHandler("getAttributionWithTimeout")) { + ThreadExecutor executor = new SingleThreadCachedScheduler("getAttributionWithTimeout"); + executor.submit(new Runnable() { + @Override + public void run() { + AdjustAttribution adjustAttribution = Util.getAttributionFromAttributionFile(context); + if (adjustAttribution != null) { + new Handler(context.getMainLooper()).post(new Runnable() { + @Override + public void run() { + attributionReadListener.onAttributionRead(adjustAttribution); + } + }); + } else { + // attribution not found locally + ActivityHandler.queueGetAttributionWithTimeout(timeoutInMilliSec, attributionReadListener, cachedAttributionReadTimeoutCallbacks, context); + } + } + }); + return; + } + activityHandler.getAttributionWithTimeout(timeoutInMilliSec, attributionReadListener); + } + /** * Called to get native SDK version string. * diff --git a/Adjust/sdk-core/src/main/java/com/adjust/sdk/AdjustTimeoutCallback.java b/Adjust/sdk-core/src/main/java/com/adjust/sdk/AdjustTimeoutCallback.java new file mode 100644 index 000000000..f868141ee --- /dev/null +++ b/Adjust/sdk-core/src/main/java/com/adjust/sdk/AdjustTimeoutCallback.java @@ -0,0 +1,41 @@ +package com.adjust.sdk; + +import com.adjust.sdk.scheduler.TimerOnce; + +public class AdjustTimeoutCallback { + private OnAdidReadListener onAdidReadListener; + private OnAttributionReadListener onAttributionReadListener; + private TimerOnce timeoutTimer; + + public AdjustTimeoutCallback(OnAdidReadListener onAdidReadListener) { + this.onAdidReadListener = onAdidReadListener; + } + + public AdjustTimeoutCallback(OnAttributionReadListener onAttributionReadListener) { + this.onAttributionReadListener = onAttributionReadListener; + } + + public void setOnAdidReadListener(OnAdidReadListener onAdidReadListener) { + this.onAdidReadListener = onAdidReadListener; + } + + public void setOnAttributionReadListener(OnAttributionReadListener onAttributionReadListener) { + this.onAttributionReadListener = onAttributionReadListener; + } + + public void setTimer(TimerOnce timer) { + this.timeoutTimer = timer; + } + + public OnAdidReadListener getOnAdidReadListener() { + return onAdidReadListener; + } + + public OnAttributionReadListener getOnAttributionReadListener() { + return onAttributionReadListener; + } + + public TimerOnce getTimeoutTimer() { + return timeoutTimer; + } +} diff --git a/Adjust/sdk-core/src/main/java/com/adjust/sdk/Constants.java b/Adjust/sdk-core/src/main/java/com/adjust/sdk/Constants.java index 52e1f5e66..a188b59c9 100644 --- a/Adjust/sdk-core/src/main/java/com/adjust/sdk/Constants.java +++ b/Adjust/sdk-core/src/main/java/com/adjust/sdk/Constants.java @@ -38,7 +38,7 @@ public interface Constants { String SCHEME = "https"; String AUTHORITY = "app.adjust.com"; - String CLIENT_SDK = "android5.4.6"; + String CLIENT_SDK = "android5.5.0"; String LOGTAG = "Adjust"; String REFTAG = "reftag"; String INSTALL_REFERRER = "install_referrer"; @@ -58,6 +58,7 @@ public interface Constants { String ATTRIBUTION_FILENAME = "AdjustAttribution"; String GLOBAL_CALLBACK_PARAMETERS_FILENAME = "AdjustGlobalCallbackParameters"; String GLOBAL_PARTNER_PARAMETERS_FILENAME = "AdjustGlobalPartnerParameters"; + String EVENT_METADATA_FILENAME = "AdjustEventMetadata"; String MALFORMED = "malformed"; String SMALL = "small"; @@ -102,4 +103,8 @@ public interface Constants { String ADJUST_PREINSTALL_CONTENT_PROVIDER_INTENT_ACTION = "com.attribution.REFERRAL_PROVIDER"; String ADJUST_PREINSTALL_FILE_SYSTEM_PATH = "/data/local/tmp/adjust.preinstall"; String EXTRA_SYSTEM_INSTALLER_REFERRER = "com.attribution.EXTRA_SYSTEM_INSTALLER_REFERRER"; + + String ADID_TIMEOUT_TIMER_NAME = "Get Adid timeout timer"; + String ATTRIBUTION_TIMEOUT_TIMER_NAME = "Get Attribution timeout timer"; + } diff --git a/Adjust/sdk-core/src/main/java/com/adjust/sdk/DeviceInfo.java b/Adjust/sdk-core/src/main/java/com/adjust/sdk/DeviceInfo.java index cf997d944..bec4d81b5 100644 --- a/Adjust/sdk-core/src/main/java/com/adjust/sdk/DeviceInfo.java +++ b/Adjust/sdk-core/src/main/java/com/adjust/sdk/DeviceInfo.java @@ -138,7 +138,7 @@ class DeviceInfo { appInstallTime = getAppInstallTime(packageInfo); appUpdateTime = getAppUpdateTime(packageInfo); uiMode = getDeviceUiMode(configuration); - if (Util.canReadPlayIds(adjustConfig)) { + if (Util.canReadAppSetId(adjustConfig)) { appSetId = Reflection.getAppSetId(context); } storeInfoFromClient = StoreInfoUtil.getStoreInfoFromClient(adjustConfig, context); diff --git a/Adjust/sdk-core/src/main/java/com/adjust/sdk/EventMetadata.java b/Adjust/sdk-core/src/main/java/com/adjust/sdk/EventMetadata.java new file mode 100644 index 000000000..e3ea0e735 --- /dev/null +++ b/Adjust/sdk-core/src/main/java/com/adjust/sdk/EventMetadata.java @@ -0,0 +1,65 @@ +package com.adjust.sdk; + + +import androidx.annotation.Nullable; + +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; +import java.io.ObjectStreamField; +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +public class EventMetadata implements Serializable { + private static final long serialVersionUID = 1L; + @SuppressWarnings("unchecked") + private static final ObjectStreamField[] serialPersistentFields = { + new ObjectStreamField("eventSequence", (Class>)(Class)Map.class), + }; + + private Map eventSequence; + + public EventMetadata() { + eventSequence = new HashMap<>(); + } + + public int incrementSequenceForEvent(String eventToken) { + @Nullable final Integer oldSequence = eventSequence.get(eventToken); + + // Sequence start at 1 when it does not exist + final int newSequence = (oldSequence != null ? oldSequence : 0) + 1; + + eventSequence.put(eventToken, newSequence); + + return newSequence; + } + + @Override + public boolean equals(Object other) { + if (other == this) return true; + if (other == null) return false; + if (getClass() != other.getClass()) return false; + EventMetadata otherEventMetadata = (EventMetadata) other; + + if (!Util.equalObject(eventSequence, otherEventMetadata.eventSequence)) return false; + + return true; + } + + @Override + public int hashCode() { + int hashCode = 17; + hashCode = Util.hashObject(eventSequence, hashCode); + return hashCode; + } + + private void writeObject(ObjectOutputStream stream) throws IOException { + stream.defaultWriteObject(); + } + + private void readObject(ObjectInputStream stream) throws ClassNotFoundException, IOException { + ObjectInputStream.GetField fields = stream.readFields(); + eventSequence = Util.readObjectField(fields, "eventSequence", new HashMap<>()); + } +} diff --git a/Adjust/sdk-core/src/main/java/com/adjust/sdk/IActivityHandler.java b/Adjust/sdk-core/src/main/java/com/adjust/sdk/IActivityHandler.java index d43a47c57..7e37483b0 100644 --- a/Adjust/sdk-core/src/main/java/com/adjust/sdk/IActivityHandler.java +++ b/Adjust/sdk-core/src/main/java/com/adjust/sdk/IActivityHandler.java @@ -93,8 +93,12 @@ public interface IActivityHandler { void getAdid(OnAdidReadListener onAdidReadListener); + void getAdidWithTimeout(long timeoutInMilliSec, OnAdidReadListener onAdidReadListener); + void getAttribution(OnAttributionReadListener onAttributionReadListener); + void getAttributionWithTimeout(long timeoutInMilliSec, OnAttributionReadListener onAttributionReadListener); + AdjustConfig getAdjustConfig(); DeviceInfo getDeviceInfo(); diff --git a/Adjust/sdk-core/src/main/java/com/adjust/sdk/PackageBuilder.java b/Adjust/sdk-core/src/main/java/com/adjust/sdk/PackageBuilder.java index a982b1c8a..6a8fd5d86 100644 --- a/Adjust/sdk-core/src/main/java/com/adjust/sdk/PackageBuilder.java +++ b/Adjust/sdk-core/src/main/java/com/adjust/sdk/PackageBuilder.java @@ -92,8 +92,8 @@ ActivityPackage buildSessionPackage() { return sessionPackage; } - ActivityPackage buildEventPackage(AdjustEvent event) { - Map parameters = getEventParameters(event); + ActivityPackage buildEventPackage(AdjustEvent event, int sequence) { + Map parameters = getEventParameters(event, sequence); ActivityPackage eventPackage = getDefaultActivityPackage(ActivityKind.EVENT); eventPackage.setPath("/event"); eventPackage.setSuffix(getEventSuffix(event)); @@ -316,7 +316,7 @@ private Map getSessionParameters() { return parameters; } - public Map getEventParameters(AdjustEvent event) { + public Map getEventParameters(AdjustEvent event, int sequence) { Map parameters = new HashMap(); deviceInfo.reloadOtherDeviceInfoParams(adjustConfig, logger); @@ -398,6 +398,7 @@ public Map getEventParameters(AdjustEvent event) { PackageBuilder.addDuration(parameters, "session_length", activityStateCopy.sessionLength); PackageBuilder.addLong(parameters, "subsession_count", activityStateCopy.subsessionCount); PackageBuilder.addDuration(parameters, "time_spent", activityStateCopy.timeSpent); + PackageBuilder.addInteger(parameters, "seq", sequence); // google play games PackageBuilder.addBoolean(parameters, "gpg_pc_enabled", deviceInfo.isGooglePlayGamesForPC ? true : null); @@ -1260,6 +1261,10 @@ private void injectFeatureFlagsWithParameters(Map parameters) { if (firstSessionDelayManager.wasSet()) { PackageBuilder.addBoolean(parameters, "ff_first_session_delay", true); } + + if (!adjustConfig.isAppSetIdReadingEnabled) { + PackageBuilder.addBoolean(parameters, "ff_app_set_id_disabled", true); + } } private void injectStoreInfoToParameters(Map parameters) { diff --git a/Adjust/sdk-core/src/main/java/com/adjust/sdk/Reflection.java b/Adjust/sdk-core/src/main/java/com/adjust/sdk/Reflection.java index bc9d715b0..03f07ff72 100644 --- a/Adjust/sdk-core/src/main/java/com/adjust/sdk/Reflection.java +++ b/Adjust/sdk-core/src/main/java/com/adjust/sdk/Reflection.java @@ -6,6 +6,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.Map; +import java.util.concurrent.TimeUnit; public class Reflection { public static Object getAdvertisingInfoObject(Context context) throws Exception { @@ -157,8 +158,8 @@ public static String getAppSetId(Context context) { Object appSetInfoObject = invokeStaticMethod("com.google.android.gms.tasks.Tasks", "await", - new Class[]{forName("com.google.android.gms.tasks.Task")}, - taskWithAppSetInfoObject); + new Class[]{forName("com.google.android.gms.tasks.Task"), long.class, TimeUnit.class}, + taskWithAppSetInfoObject, 1L, TimeUnit.SECONDS); return (String) invokeInstanceMethod(appSetInfoObject, "getId", null); diff --git a/Adjust/sdk-core/src/main/java/com/adjust/sdk/Util.java b/Adjust/sdk-core/src/main/java/com/adjust/sdk/Util.java index 84562f3de..08be554a5 100644 --- a/Adjust/sdk-core/src/main/java/com/adjust/sdk/Util.java +++ b/Adjust/sdk-core/src/main/java/com/adjust/sdk/Util.java @@ -685,6 +685,13 @@ public static boolean canReadNonPlayIds(final AdjustConfig adjustConfig) { return !adjustConfig.coppaComplianceEnabled && !adjustConfig.playStoreKidsComplianceEnabled; } + public static boolean canReadAppSetId(final AdjustConfig adjustConfig) { + if (!adjustConfig.isAppSetIdReadingEnabled) { + return false; + } + return canReadPlayIds(adjustConfig); + } + public static boolean isGooglePlayGamesForPC(final Context context) { PackageManager pm = context.getPackageManager(); return pm.hasSystemFeature("com.google.android.play.feature.HPE_EXPERIENCE"); @@ -800,4 +807,25 @@ public static AdjustAttribution attributionFromJson(final JSONObject jsonObject, return attribution; } + + public static String getAdidFromActivityStateFile(final Context context) { + ActivityState activityState = Util.readObject( + context, + Constants.ACTIVITY_STATE_FILENAME, + "Activity state", + ActivityState.class); + if (activityState == null) { + return null; + } else { + return activityState.adid; + } + } + + public static AdjustAttribution getAttributionFromAttributionFile(final Context context) { + return Util.readObject( + context, + Constants.ATTRIBUTION_FILENAME, + "Attribution", + AdjustAttribution.class); + } } \ No newline at end of file diff --git a/Adjust/tests/test-app-core/build.gradle b/Adjust/tests/test-app-core/build.gradle index 236939df5..c508abb36 100644 --- a/Adjust/tests/test-app-core/build.gradle +++ b/Adjust/tests/test-app-core/build.gradle @@ -30,7 +30,8 @@ dependencies { implementation "androidx.constraintlayout:constraintlayout:2.1.1" implementation 'com.google.android.gms:play-services-ads-identifier:18.2.0' implementation 'com.android.installreferrer:installreferrer:2.2' - implementation 'com.adjust.signature:adjust-android-signature:3.61.0' + implementation 'com.adjust.signature:adjust-android-signature:3.62.0' + implementation 'com.google.android.gms:play-services-appset:16.1.0' implementation project(':sdk-core') implementation project(':tests:test-options') diff --git a/Adjust/tests/test-app-core/src/main/java/com/adjust/testapp/AdjustCommandExecutor.java b/Adjust/tests/test-app-core/src/main/java/com/adjust/testapp/AdjustCommandExecutor.java index eb9c70b1c..11c723a26 100644 --- a/Adjust/tests/test-app-core/src/main/java/com/adjust/testapp/AdjustCommandExecutor.java +++ b/Adjust/tests/test-app-core/src/main/java/com/adjust/testapp/AdjustCommandExecutor.java @@ -27,6 +27,7 @@ import com.adjust.sdk.AdjustTestOptions; import com.adjust.sdk.AdjustThirdPartySharing; import com.adjust.sdk.LogLevel; +import com.adjust.sdk.OnAdidReadListener; import com.adjust.sdk.OnAttributionChangedListener; import com.adjust.sdk.OnDeeplinkResolvedListener; import com.adjust.sdk.OnDeferredDeeplinkResponseListener; @@ -95,11 +96,17 @@ public void executeCommand(final Command sentCommand) { case "verifyTrack": verifyTrack(); break; case "processDeeplink" : processDeeplink(); break; case "attributionGetter" : attributionGetter(); break; + case "attributionGetterWithTimeout" : attributionGetterWithTimeout(); break; + case "adidGetter" : adidGetter(); break; + case "adidGetterWithTimeout" : adidGetterWithTimeout(); break; case "getLastDeeplink" : getLastDeeplink(); break; case "endFirstSessionDelay" : endFirstSessionDelay(); break; case "coppaComplianceInDelay" : coppaComplianceInDelay(); break; case "playStoreKidsComplianceInDelay" : playStoreKidsComplianceInDelay(); break; case "externalDeviceIdInDelay" : externalDeviceIdInDelay(); break; + case "sdkVersionGetter" : sdkVersionGetter(); break; + case "googleAdIdGetter" : googleAdIdGetter(); break; + case "amazonAdIdGetter" : amazonAdIdGetter(); break; //case "testBegin": testBegin(); break; // case "testEnd": testEnd(); break; } @@ -488,6 +495,14 @@ public boolean launchReceivedDeeplink(Uri deeplink) { adjustConfig.enableFirstSessionDelay(); } } + + if (command.containsParameter("appSetIdReadingEnabled")) { + String appSetIdReadingEnabledS = command.getFirstParameterValue("appSetIdReadingEnabled"); + boolean appSetIdReadingEnabled = "true".equals(appSetIdReadingEnabledS); + if (!appSetIdReadingEnabled) { + adjustConfig.disableAppSetIdReading(); + } + } } private void start() { @@ -886,8 +901,11 @@ public void onDeeplinkResolved(String resolvedLink) { } private void attributionGetter() { + String testCallbackId = command.getFirstParameterValue("testCallbackId"); + Adjust.getAttribution(attribution -> { Map fields = new HashMap<>(); + fields.put("test_callback_id", testCallbackId); if (attribution.trackerToken != null) fields.put("tracker_token", attribution.trackerToken); if (attribution.trackerName != null) @@ -918,12 +936,82 @@ private void attributionGetter() { }); } + private void attributionGetterWithTimeout() { + long timeout = Long.parseLong(command.getFirstParameterValue("timeout")); + String testCallbackId = command.getFirstParameterValue("testCallbackId"); + + Adjust.getAttributionWithTimeout(context, timeout, attribution -> { + Map fields = new HashMap<>(); + fields.put("test_callback_id", testCallbackId); + if (attribution != null) { + if (attribution.trackerToken != null) + fields.put("tracker_token", attribution.trackerToken); + if (attribution.trackerName != null) + fields.put("tracker_name", attribution.trackerName); + if (attribution.network != null) + fields.put("network", attribution.network); + if (attribution.campaign != null) + fields.put("campaign", attribution.campaign); + if (attribution.adgroup != null) + fields.put("adgroup", attribution.adgroup); + if (attribution.creative != null) + fields.put("creative", attribution.creative); + if (attribution.clickLabel != null) + fields.put("click_label", attribution.clickLabel); + if (attribution.costType != null) + fields.put("cost_type", attribution.costType); + if (attribution.costAmount != null) + fields.put("cost_amount", attribution.costAmount.toString()); + if (attribution.costCurrency != null) + fields.put("cost_currency", attribution.costCurrency); + if (attribution.fbInstallReferrer != null) + fields.put("fb_install_referrer", attribution.fbInstallReferrer); + if (attribution.jsonResponse != null) + fields.put("json_response", attribution.jsonResponse); + + MainActivity.testLibrary.setInfoToSend(fields); + } else { + MainActivity.testLibrary.addInfoToSend("attribution", "null"); + } + + MainActivity.testLibrary.sendInfoToServer(basePath); + }); + } + + private void adidGetter() { + String testCallbackId = command.getFirstParameterValue("testCallbackId"); + + Adjust.getAdid(adid -> { + MainActivity.testLibrary.addInfoToSend("adid", adid); + MainActivity.testLibrary.addInfoToSend("test_callback_id", testCallbackId); + MainActivity.testLibrary.sendInfoToServer(basePath); + }); + } + + private void adidGetterWithTimeout() { + long timeout = Long.parseLong(command.getFirstParameterValue("timeout")); + String testCallbackId = command.getFirstParameterValue("testCallbackId"); + + Adjust.getAdidWithTimeout(context, timeout, adid -> { + if (adid != null) { + MainActivity.testLibrary.addInfoToSend("adid", adid); + } else { + MainActivity.testLibrary.addInfoToSend("adid", "null"); + } + MainActivity.testLibrary.addInfoToSend("test_callback_id", testCallbackId); + MainActivity.testLibrary.sendInfoToServer(basePath); + }); + } + private void getLastDeeplink() { + String testCallbackId = command.getFirstParameterValue("testCallbackId"); final String localBasePath = basePath; + Adjust.getLastDeeplink(context, new OnLastDeeplinkReadListener() { @Override public void onLastDeeplinkRead(Uri deeplink) { MainActivity.testLibrary.addInfoToSend("last_deeplink", deeplink == null ? "" : deeplink.toString()); + MainActivity.testLibrary.addInfoToSend("test_callback_id", testCallbackId); MainActivity.testLibrary.sendInfoToServer(localBasePath); } }); @@ -957,6 +1045,36 @@ private void externalDeviceIdInDelay() { String externalDeviceId = command.getFirstParameterValue("externalDeviceId"); Adjust.setExternalDeviceIdInDelay(externalDeviceId); } + + private void sdkVersionGetter() { + String testCallbackId = command.getFirstParameterValue("testCallbackId"); + + Adjust.getSdkVersion(sdkVersion -> { + MainActivity.testLibrary.addInfoToSend("sdk_version", sdkVersion); + MainActivity.testLibrary.addInfoToSend("test_callback_id", testCallbackId); + MainActivity.testLibrary.sendInfoToServer(basePath); + }); + } + + private void googleAdIdGetter() { + String testCallbackId = command.getFirstParameterValue("testCallbackId"); + + Adjust.getGoogleAdId(context, googleAdId -> { + MainActivity.testLibrary.addInfoToSend("gps_adid", googleAdId); + MainActivity.testLibrary.addInfoToSend("test_callback_id", testCallbackId); + MainActivity.testLibrary.sendInfoToServer(basePath); + }); + } + + private void amazonAdIdGetter() { + String testCallbackId = command.getFirstParameterValue("testCallbackId"); + + Adjust.getAmazonAdId(context, amazonAdId -> { + MainActivity.testLibrary.addInfoToSend("fire_adid", amazonAdId); + MainActivity.testLibrary.addInfoToSend("test_callback_id", testCallbackId); + MainActivity.testLibrary.sendInfoToServer(basePath); + }); + } /* private void testBegin() { if (command.containsParameter("teardown")) { diff --git a/Adjust/tests/test-app-webbridge/build.gradle b/Adjust/tests/test-app-webbridge/build.gradle index 74137f277..394c27117 100644 --- a/Adjust/tests/test-app-webbridge/build.gradle +++ b/Adjust/tests/test-app-webbridge/build.gradle @@ -32,7 +32,7 @@ dependencies { implementation "androidx.constraintlayout:constraintlayout:2.1.1" implementation 'com.google.android.gms:play-services-ads-identifier:18.2.0' implementation 'com.android.installreferrer:installreferrer:2.2' - implementation 'com.adjust.signature:adjust-android-signature:3.61.0' + implementation 'com.adjust.signature:adjust-android-signature:3.62.0' implementation project(':sdk-core') implementation project(':plugins:sdk-plugin-webbridge') diff --git a/Adjust/tests/test-app-webbridge/src/main/assets/command_executor.js b/Adjust/tests/test-app-webbridge/src/main/assets/command_executor.js index 58834a39e..e0d80374c 100644 --- a/Adjust/tests/test-app-webbridge/src/main/assets/command_executor.js +++ b/Adjust/tests/test-app-webbridge/src/main/assets/command_executor.js @@ -136,10 +136,16 @@ AdjustCommandExecutor.prototype.executeCommand = function(command, idx) { case "measurementConsent" : this.measurementConsent(command.params); break; case "trackAdRevenue" : this.trackAdRevenue(command.params); break; case "attributionGetter" : this.attributionGetter(command.params); break; + case "attributionGetterWithTimeout" : this.attributionGetterWithTimeout(command.params); break; + case "adidGetter" : this.adidGetter(command.params); break; + case "adidGetterWithTimeout" : this.adidGetterWithTimeout(command.params); break; case "endFirstSessionDelay" : this.endFirstSessionDelay(command.params); break; case "coppaComplianceInDelay" : this.coppaComplianceInDelay(command.params); break; case "playStoreKidsComplianceInDelay" : this.playStoreKidsComplianceInDelay(command.params); break; case "externalDeviceIdInDelay" : this.externalDeviceIdInDelay(command.params); break; + case "sdkVersionGetter" : this.sdkVersionGetter(command.params); break; + case "googleAdIdGetter" : this.googleAdIdGetter(command.params); break; + case "amazonAdIdGetter" : this.amazonAdIdGetter(command.params); break; break; } @@ -692,25 +698,119 @@ AdjustCommandExecutor.prototype.sendReferrer = function(params) { }; AdjustCommandExecutor.prototype.attributionGetter = function(params) { + var testCallbackId = getFirstParameterValue(params, 'testCallbackId'); + var basePath = this.basePath; + + Adjust.getAttribution(function(attribution) { + TestLibrary.addInfoToSend("tracker_token", attribution.trackerToken); + TestLibrary.addInfoToSend("tracker_name", attribution.trackerName); + TestLibrary.addInfoToSend("network", attribution.network); + TestLibrary.addInfoToSend("campaign", attribution.campaign); + TestLibrary.addInfoToSend("adgroup", attribution.adgroup); + TestLibrary.addInfoToSend("creative", attribution.creative); + TestLibrary.addInfoToSend("click_label", attribution.clickLabel); + TestLibrary.addInfoToSend("cost_type", attribution.costType); + TestLibrary.addInfoToSend("cost_amount", attribution.costAmount); + TestLibrary.addInfoToSend("cost_currency", attribution.costCurrency); + TestLibrary.addInfoToSend("fb_install_referrer", attribution.fbInstallReferrer); + TestLibrary.addInfoToSend("json_response", JSON.stringify(attribution.jsonResponse)); + + TestLibrary.addInfoToSend("test_callback_id", testCallbackId); + TestLibrary.sendInfoToServer(basePath); + }); +}; + +AdjustCommandExecutor.prototype.sdkVersionGetter = function(params) { + var testCallbackId = getFirstParameterValue(params, 'testCallbackId'); + var basePath = this.basePath; + + Adjust.getSdkVersion(function(sdkVersion) { + TestLibrary.addInfoToSend("sdk_version", sdkVersion); + TestLibrary.addInfoToSend("test_callback_id", testCallbackId); + TestLibrary.sendInfoToServer(basePath); + }); +}; + +AdjustCommandExecutor.prototype.googleAdIdGetter = function(params) { + var testCallbackId = getFirstParameterValue(params, 'testCallbackId'); + var basePath = this.basePath; + + Adjust.getGoogleAdId(function(googleAdId) { + TestLibrary.addInfoToSend("gps_adid", googleAdId); + TestLibrary.addInfoToSend("test_callback_id", testCallbackId); + TestLibrary.sendInfoToServer(basePath); + }); +}; + +AdjustCommandExecutor.prototype.amazonAdIdGetter = function(params) { + var testCallbackId = getFirstParameterValue(params, 'testCallbackId'); + var basePath = this.basePath; + + Adjust.getAmazonAdId(function(amazonAdId) { + TestLibrary.addInfoToSend("fire_adid", amazonAdId); + TestLibrary.addInfoToSend("test_callback_id", testCallbackId); + TestLibrary.sendInfoToServer(basePath); + }); +}; + +AdjustCommandExecutor.prototype.attributionGetterWithTimeout = function(params) { + var timeoutS = getFirstParameterValue(params, 'timeout'); + var timeout = parseInt(timeoutS); + var testCallbackId = getFirstParameterValue(params, 'testCallbackId'); var basePath = this.basePath; - Adjust.getAttribution(function(attribution) { - TestLibrary.addInfoToSend("tracker_token", attribution.trackerToken); - TestLibrary.addInfoToSend("tracker_name", attribution.trackerName); - TestLibrary.addInfoToSend("network", attribution.network); - TestLibrary.addInfoToSend("campaign", attribution.campaign); - TestLibrary.addInfoToSend("adgroup", attribution.adgroup); - TestLibrary.addInfoToSend("creative", attribution.creative); - TestLibrary.addInfoToSend("click_label", attribution.clickLabel); - TestLibrary.addInfoToSend("cost_type", attribution.costType); - TestLibrary.addInfoToSend("cost_amount", attribution.costAmount); - TestLibrary.addInfoToSend("cost_currency", attribution.costCurrency); - TestLibrary.addInfoToSend("fb_install_referrer", attribution.fbInstallReferrer); - TestLibrary.addInfoToSend("json_response", JSON.stringify(attribution.jsonResponse)); + Adjust.getAttributionWithTimeout(timeout, function(attribution) { + if (attribution != null) { + TestLibrary.addInfoToSend("tracker_token", attribution.trackerToken); + TestLibrary.addInfoToSend("tracker_name", attribution.trackerName); + TestLibrary.addInfoToSend("network", attribution.network); + TestLibrary.addInfoToSend("campaign", attribution.campaign); + TestLibrary.addInfoToSend("adgroup", attribution.adgroup); + TestLibrary.addInfoToSend("creative", attribution.creative); + TestLibrary.addInfoToSend("click_label", attribution.clickLabel); + TestLibrary.addInfoToSend("cost_type", attribution.costType); + TestLibrary.addInfoToSend("cost_amount", attribution.costAmount); + TestLibrary.addInfoToSend("cost_currency", attribution.costCurrency); + TestLibrary.addInfoToSend("fb_install_referrer", attribution.fbInstallReferrer); + TestLibrary.addInfoToSend("json_response", JSON.stringify(attribution.jsonResponse)); + } else { + TestLibrary.addInfoToSend("attribution", "null"); + } + + TestLibrary.addInfoToSend("test_callback_id", testCallbackId); TestLibrary.sendInfoToServer(basePath); }); }; +AdjustCommandExecutor.prototype.adidGetter = function(params) { + var testCallbackId = getFirstParameterValue(params, 'testCallbackId'); + var basePath = this.basePath; + + Adjust.getAdid(function(adid) { + TestLibrary.addInfoToSend("adid", adid); + TestLibrary.addInfoToSend("test_callback_id", testCallbackId); + TestLibrary.sendInfoToServer(basePath); + }); +}; + +AdjustCommandExecutor.prototype.adidGetterWithTimeout = function(params) { + var timeoutS = getFirstParameterValue(params, 'timeout'); + var timeout = parseInt(timeoutS); + var testCallbackId = getFirstParameterValue(params, 'testCallbackId'); + var basePath = this.basePath; + + Adjust.getAdidWithTimeout(timeout, function(adid) { + if (adid != null) { + TestLibrary.addInfoToSend("adid", adid); + } else { + TestLibrary.addInfoToSend("adid", "null"); + } + + TestLibrary.addInfoToSend("test_callback_id", testCallbackId); + TestLibrary.sendInfoToServer(basePath); + }); +}; + //Util //====================== function getValueFromKey(params, key) { diff --git a/Adjust/tests/test-library/src/main/java/com/adjust/test/TestLibrary.java b/Adjust/tests/test-library/src/main/java/com/adjust/test/TestLibrary.java index e52dad324..da1a5c7c7 100644 --- a/Adjust/tests/test-library/src/main/java/com/adjust/test/TestLibrary.java +++ b/Adjust/tests/test-library/src/main/java/com/adjust/test/TestLibrary.java @@ -147,21 +147,38 @@ public void run() { } public void addInfoToSend(String key, String value) { - if (infoToServer == null) { - infoToServer = new HashMap(); + synchronized (this) { + if (infoToServer == null) { + infoToServer = new HashMap(); + } + infoToServer.put(key, value); } - infoToServer.put(key, value); } public void setInfoToSend(Map info) { - infoToServer = info; + synchronized (this) { + infoToServer = info; + } } public void sendInfoToServer(final String basePath) { + // Create a snapshot of the current infoToServer map to avoid race conditions + // when multiple callbacks execute concurrently + final Map infoSnapshot; + synchronized (this) { + if (infoToServer == null || infoToServer.isEmpty()) { + infoSnapshot = new HashMap(); + } else { + infoSnapshot = new HashMap(infoToServer); + } + // Clear the shared map immediately so next callback starts fresh + infoToServer = null; + } + executor.submit(new Runnable() { @Override public void run() { - sendInfoToServerI(basePath); + sendInfoToServerI(basePath, infoSnapshot); } }); } @@ -178,9 +195,8 @@ private void startTestSessionI(String clientSdk) { readResponseI(response); } - private void sendInfoToServerI(String basePath) { - Networking.Response response = networking.sendPost("/test_info", basePath, infoToServer); - infoToServer = null; + private void sendInfoToServerI(String basePath, Map infoToSend) { + Networking.Response response = networking.sendPost("/test_info", basePath, infoToSend); readResponseI(response); } diff --git a/CHANGELOG.md b/CHANGELOG.md index 62b79e237..29681721e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +### Version 5.5.0 (5th December 2025) +#### Added +- Added `getAdidWithTimeout` method to the `Adjust` API to allow retrieving the ADID with a specified timeout. If the value is not obtained in time, null is returned. +- Added `getAttributionWithTimeout` method to the `Adjust` API to allow retrieving the current attribution information with a specified timeout. If the value is not obtained in time, null is returned. +- Added `disableAppSetIdReading` method to `AdjustConfig` to allow disabling the SDK’s reading of the app set ID. + +#### Changed +- Updated the Adjust Signature library version to 3.62.0. + +--- + ### Version 5.4.6 (7th November 2025) #### Changed - Refactored LVL plugin to use direct `Binder` interface instead of `AIDL`. diff --git a/VERSION b/VERSION index bb0915fd5..d50359de1 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.4.6 +5.5.0