diff --git a/Adjust/adjust/build.gradle b/Adjust/adjust/build.gradle index df3c67eb4..8cfd981b1 100644 --- a/Adjust/adjust/build.gradle +++ b/Adjust/adjust/build.gradle @@ -1,16 +1,16 @@ apply plugin: 'com.android.library' def getVersionName() { - return "4.12.1" + return "4.12.2" } android { - compileSdkVersion 26 - buildToolsVersion '26.0.2' + compileSdkVersion 27 + buildToolsVersion '27.0.3' defaultConfig { minSdkVersion 9 - targetSdkVersion 26 + targetSdkVersion 27 versionCode 1 versionName getVersionName() consumerProguardFiles 'adjust-proguard-rules.txt' diff --git a/Adjust/adjust/src/main/java/com/adjust/sdk/ActivityHandler.java b/Adjust/adjust/src/main/java/com/adjust/sdk/ActivityHandler.java index be3097f7c..a93c13430 100644 --- a/Adjust/adjust/src/main/java/com/adjust/sdk/ActivityHandler.java +++ b/Adjust/adjust/src/main/java/com/adjust/sdk/ActivityHandler.java @@ -53,6 +53,7 @@ public class ActivityHandler implements IActivityHandler { private TimerOnce backgroundTimer; private TimerOnce delayStartTimer; private InternalState internalState; + private String basePath; private DeviceInfo deviceInfo; private AdjustConfig adjustConfig; // always valid after construction @@ -63,7 +64,7 @@ public class ActivityHandler implements IActivityHandler { private InstallReferrer installReferrer; @Override - public void teardown(boolean deleteState) { + public void teardown() { if (backgroundTimer != null) { backgroundTimer.teardown(); } @@ -79,7 +80,7 @@ public void teardown(boolean deleteState) { } catch(SecurityException se) {} } if (packageHandler != null) { - packageHandler.teardown(deleteState); + packageHandler.teardown(); } if (attributionHandler != null) { attributionHandler.teardown(); @@ -96,14 +97,9 @@ public void teardown(boolean deleteState) { } } - teardownActivityStateS(deleteState); - teardownAttributionS(deleteState); - teardownAllSessionParametersS(deleteState); - - if (deleteState) { - SharedPreferencesManager sharedPreferencesManager = new SharedPreferencesManager(getContext()); - sharedPreferencesManager.clear(); - } + teardownActivityStateS(); + teardownAttributionS(); + teardownAllSessionParametersS(); packageHandler = null; logger = null; @@ -119,6 +115,16 @@ public void teardown(boolean deleteState) { sessionParameters = null; } + static void deleteState(Context context) { + deleteActivityState(context); + deleteAttribution(context); + deleteSessionCallbackParameters(context); + deleteSessionPartnerParameters(context); + + SharedPreferencesManager sharedPreferencesManager = new SharedPreferencesManager(context); + sharedPreferencesManager.clear(); + } + public class InternalState { boolean enabled; boolean offline; @@ -127,6 +133,7 @@ public class InternalState { boolean updatePackages; boolean firstLaunch; boolean sessionResponseProcessed; + boolean firstSdkStart; public boolean isEnabled() { return enabled; @@ -175,6 +182,14 @@ public boolean isNotFirstLaunch() { public boolean hasSessionResponseNotBeenProcessed() { return !sessionResponseProcessed; } + + public boolean hasFirstSdkStartOcurred() { + return firstSdkStart; + } + + public boolean hasFirstSdkStartNotOcurred() { + return !firstSdkStart; + } } private ActivityHandler(AdjustConfig adjustConfig) { @@ -200,6 +215,8 @@ private ActivityHandler(AdjustConfig adjustConfig) { internalState.updatePackages = false; // does not have the session response by default internalState.sessionResponseProcessed = false; + // does not have first start by default + internalState.firstSdkStart = false; scheduledExecutor.submit(new Runnable() { @Override @@ -311,7 +328,7 @@ public void trackEvent(final AdjustEvent event) { scheduledExecutor.submit(new Runnable() { @Override public void run() { - if (activityState == null) { + if (internalState.hasFirstSdkStartNotOcurred()) { logger.warn("Event tracked before first activity resumed.\n" + "If it was triggered in the Application class, it might timestamp or even send an install long before the user opens the app.\n" + "Please check https://github.com/adjust/android_sdk#can-i-trigger-an-event-at-application-launch for more information."); @@ -564,7 +581,7 @@ public void run() { sharedPreferencesManager.savePushToken(token); } - if (activityState == null) { + if (internalState.hasFirstSdkStartNotOcurred()) { // No install has been tracked so far. // Push token is saved, ready for the session package to pick it up. return; @@ -611,6 +628,11 @@ public AdjustAttribution getAttribution() { return attribution; } + @Override + public String getBasePath() { + return this.basePath; + } + public ActivityPackage getAttributionPackageI() { long now = System.currentTimeMillis(); PackageBuilder attributionBuilder = new PackageBuilder(adjustConfig, @@ -653,7 +675,7 @@ public void run(ActivityHandler activityHandler) { }); } - if (activityState != null) { + if (internalState.hasFirstSdkStartOcurred()) { internalState.enabled = activityState.enabled; internalState.updatePackages = activityState.updatePackages; internalState.firstLaunch = false; @@ -687,14 +709,17 @@ public void run(ActivityHandler activityHandler) { if (adjustConfig.pushToken != null) { logger.info("Push token: '%s'", adjustConfig.pushToken); - if (activityState != null) { + if (internalState.hasFirstSdkStartOcurred()) { + // since sdk has already started, try to send current push token setPushToken(adjustConfig.pushToken, false); } else { + // since sdk has not yet started, save current push token for when it does SharedPreferencesManager sharedPreferencesManager = new SharedPreferencesManager(getContext()); sharedPreferencesManager.savePushToken(adjustConfig.pushToken); } } else { - if (activityState != null) { + // since sdk has already started, check if there is a saved push from previous runs + if (internalState.hasFirstSdkStartOcurred()) { SharedPreferencesManager sharedPreferencesManager = new SharedPreferencesManager(getContext()); String savedPushToken = sharedPreferencesManager.getPushToken(); @@ -723,7 +748,7 @@ public void run() { } // configure delay start timer - if (activityState == null && + if (internalState.hasFirstSdkStartNotOcurred() && adjustConfig.delayStart != null && adjustConfig.delayStart > 0.0) { @@ -739,6 +764,8 @@ public void run() { UtilNetworking.setUserAgent(adjustConfig.userAgent); + this.basePath = adjustConfig.basePath; + packageHandler = AdjustFactory.getPackageHandler(this, adjustConfig.context, toSendI(false)); ActivityPackage attributionPackage = getAttributionPackageI(); @@ -792,9 +819,14 @@ private void preLaunchActionsI(List preLaunchActionsArray) } private void startI() { + // check if it's the first sdk start + if (internalState.hasFirstSdkStartNotOcurred()) { + startFirstSessionI(); + return; + } + // it shouldn't start if it was disabled after a first session - if (activityState != null - && !activityState.enabled) { + if (!activityState.enabled) { return; } @@ -805,32 +837,38 @@ private void startI() { checkAttributionStateI(); } - private void processSessionI() { + private void startFirstSessionI() { + // still update handlers status + updateHandlersStatusAndSendI(); + + activityState = new ActivityState(); + internalState.firstSdkStart = true; + long now = System.currentTimeMillis(); - // very first session - if (activityState == null) { - activityState = new ActivityState(); + SharedPreferencesManager sharedPreferencesManager = new SharedPreferencesManager(getContext()); + activityState.pushToken = sharedPreferencesManager.getPushToken(); - // activityState.pushToken = adjustConfig.pushToken; - SharedPreferencesManager sharedPreferencesManager = new SharedPreferencesManager(getContext()); - activityState.pushToken = sharedPreferencesManager.getPushToken(); + // track the first session package only if it's enabled + if (internalState.isEnabled()) { + activityState.sessionCount = 1; // this is the first session + transferSessionPackageI(now); - // track the first session package only if it's enabled - if (internalState.isEnabled()) { - activityState.sessionCount = 1; // this is the first session - transferSessionPackageI(now); - } + checkAfterNewStartI(sharedPreferencesManager); + } - activityState.resetSessionAttributes(now); - activityState.enabled = internalState.isEnabled(); - activityState.updatePackages = internalState.itHasToUpdatePackages(); + activityState.resetSessionAttributes(now); + activityState.enabled = internalState.isEnabled(); + activityState.updatePackages = internalState.itHasToUpdatePackages(); - writeActivityStateI(); - sharedPreferencesManager.removePushToken(); + writeActivityStateI(); + sharedPreferencesManager.removePushToken(); - return; - } + // don't check attribution right after first sdk start + } + + private void processSessionI() { + long now = System.currentTimeMillis(); long lastInterval = now - activityState.lastActivity; @@ -844,6 +882,7 @@ private void processSessionI() { // new session if (lastInterval > SESSION_INTERVAL) { trackNewSessionI(now); + checkAfterNewStartI(); return; } @@ -856,6 +895,10 @@ private void processSessionI() { activityState.subsessionCount, activityState.sessionCount); writeActivityStateI(); + + // Try to check if there's new referrer information. + installReferrer.startConnection(); + return; } @@ -1173,7 +1216,7 @@ private void setEnabledI(boolean enabled) { // save new enabled state in internal state internalState.enabled = enabled; - if (activityState == null) { + if (internalState.hasFirstSdkStartNotOcurred()) { updateStatusI(!enabled, "Handlers will start as paused due to the SDK being disabled", "Handlers will still start as paused", @@ -1189,19 +1232,7 @@ private void setEnabledI(boolean enabled) { long now = System.currentTimeMillis(); trackNewSessionI(now); } - - // check if there is a saved push token to send - String pushToken = sharedPreferencesManager.getPushToken(); - - if (pushToken != null && !pushToken.equals(activityState.pushToken)) { - setPushToken(pushToken, true); - } - - // check if there are token to send - Object referrers = sharedPreferencesManager.getRawReferrerArray(); - if (referrers != null) { - sendReftagReferrer(); - } + checkAfterNewStartI(sharedPreferencesManager); } activityState.enabled = enabled; @@ -1213,6 +1244,32 @@ private void setEnabledI(boolean enabled) { "Resuming handlers due to SDK being enabled"); } + + private void checkAfterNewStartI() { + SharedPreferencesManager sharedPreferencesManager = new SharedPreferencesManager(getContext()); + checkAfterNewStartI(sharedPreferencesManager); + } + + private void checkAfterNewStartI(SharedPreferencesManager sharedPreferencesManager) { + // check if there is a saved push token to send + String pushToken = sharedPreferencesManager.getPushToken(); + + if (pushToken != null && !pushToken.equals(activityState.pushToken)) { + // queue set push token + setPushToken(pushToken, true); + } + + // check if there are token to send + Object referrers = sharedPreferencesManager.getRawReferrerArray(); + if (referrers != null) { + // queue send referrer tag + sendReftagReferrer(); + } + + // try to read and send the install referrer + installReferrer.startConnection(); + } + private void setOfflineModeI(boolean offline) { // compare with the internal state if (!hasChangedStateI(internalState.isOffline(), offline, @@ -1223,7 +1280,7 @@ private void setOfflineModeI(boolean offline) { internalState.offline = offline; - if (activityState == null) { + if (internalState.hasFirstSdkStartNotOcurred()) { updateStatusI(offline, "Handlers will start paused due to SDK being offline", "Handlers will still start as paused", @@ -1287,6 +1344,9 @@ private void sendReftagReferrerI() { if (!isEnabledI()) { return; } + if (internalState.hasFirstSdkStartNotOcurred()) { + return; + } sdkClickHandler.sendReftagReferrers(); } @@ -1349,14 +1409,12 @@ private void updateHandlersStatusAndSendI() { resumeSendingI(); - // try to send if it's the first launch and it hasn't received the session response - // even if event buffering is enabled - if (internalState.isFirstLaunch() && internalState.hasSessionResponseNotBeenProcessed()) { - packageHandler.sendFirstPackage(); - } - - // try to send - if (!adjustConfig.eventBufferingEnabled) { + // if event buffering is not enabled + if (!adjustConfig.eventBufferingEnabled || + // or if it's the first launch and it hasn't received the session response + (internalState.isFirstLaunch() && internalState.hasSessionResponseNotBeenProcessed())) + { + // try to send packageHandler.sendFirstPackage(); } } @@ -1703,6 +1761,9 @@ private void readActivityStateI(Context context) { logger.error("Failed to read %s file (%s)", ACTIVITY_STATE_NAME, e.getMessage()); activityState = null; } + if (activityState != null) { + internalState.firstSdkStart = true; + } } private void readAttributionI(Context context) { @@ -1747,14 +1808,11 @@ private void writeActivityStateI() { } } - private void teardownActivityStateS(boolean toDelete) { + private void teardownActivityStateS() { synchronized (ActivityState.class) { if (activityState == null) { return; } - if (toDelete && adjustConfig != null && adjustConfig.context != null) { - deleteActivityState(adjustConfig.context); - } activityState = null; } } @@ -1768,14 +1826,11 @@ private void writeAttributionI() { } } - private void teardownAttributionS(boolean toDelete) { + private void teardownAttributionS() { synchronized (AdjustAttribution.class) { if (attribution == null) { return; } - if (toDelete && adjustConfig != null && adjustConfig.context != null) { - deleteAttribution(adjustConfig.context); - } attribution = null; } } @@ -1798,15 +1853,11 @@ private void writeSessionPartnerParametersI() { } } - private void teardownAllSessionParametersS(boolean toDelete) { + private void teardownAllSessionParametersS() { synchronized (SessionParameters.class) { if (sessionParameters == null) { return; } - if (toDelete && adjustConfig != null && adjustConfig.context != null) { - deleteSessionCallbackParameters(adjustConfig.context); - deleteSessionPartnerParameters(adjustConfig.context); - } sessionParameters = null; } } @@ -1842,8 +1893,8 @@ private boolean checkOrderIdI(String orderId) { } private boolean checkActivityStateI(ActivityState activityState) { - if (activityState == null) { - logger.error("Missing activity state"); + if (internalState.hasFirstSdkStartNotOcurred()) { + logger.error("Sdk did not yet start"); return false; } return true; diff --git a/Adjust/adjust/src/main/java/com/adjust/sdk/Adjust.java b/Adjust/adjust/src/main/java/com/adjust/sdk/Adjust.java index dd3dbe1fd..7c37d83d3 100644 --- a/Adjust/adjust/src/main/java/com/adjust/sdk/Adjust.java +++ b/Adjust/adjust/src/main/java/com/adjust/sdk/Adjust.java @@ -138,4 +138,16 @@ public static AdjustAttribution getAttribution() { AdjustInstance adjustInstance = Adjust.getDefaultInstance(); return adjustInstance.getAttribution(); } + + public static void setTestOptions(AdjustTestOptions testOptions) { + if (testOptions.teardown != null && testOptions.teardown.booleanValue()) { + if (defaultInstance != null) { + defaultInstance.teardown(); + } + defaultInstance = null; + AdjustFactory.teardown(testOptions.context); + } + AdjustInstance adjustInstance = Adjust.getDefaultInstance(); + adjustInstance.setTestOptions(testOptions); + } } diff --git a/Adjust/adjust/src/main/java/com/adjust/sdk/AdjustConfig.java b/Adjust/adjust/src/main/java/com/adjust/sdk/AdjustConfig.java index 9de97494f..f004d9ffc 100644 --- a/Adjust/adjust/src/main/java/com/adjust/sdk/AdjustConfig.java +++ b/Adjust/adjust/src/main/java/com/adjust/sdk/AdjustConfig.java @@ -8,6 +8,7 @@ * Created by pfms on 06/11/14. */ public class AdjustConfig { + String basePath; Context context; String appToken; String environment; @@ -46,6 +47,7 @@ public AdjustConfig(Context context, String appToken, String environment, boolea init(context, appToken, environment, allowSuppressLogLevel); } + // Beware that some of these values might be null. isValid() would check their validity later. private void init(Context context, String appToken, String environment, boolean allowSuppressLogLevel) { logger = AdjustFactory.getLogger(); // default values @@ -55,11 +57,12 @@ private void init(Context context, String appToken, String environment, boolean setLogLevel(LogLevel.INFO, environment); } - if (!isValid(context, appToken, environment)) { - return; + // Always use application context + if (context != null) { + context = context.getApplicationContext(); } - this.context = context.getApplicationContext(); + this.context = context; this.appToken = appToken; this.environment = environment; @@ -144,10 +147,6 @@ public void setReadMobileEquipmentIdentity(boolean readMobileEquipmentIdentity) } public boolean isValid() { - return appToken != null; - } - - private boolean isValid(Context context, String appToken, String environment) { if (!checkAppToken(appToken)) return false; if (!checkEnvironment(environment)) return false; if (!checkContext(context)) return false; diff --git a/Adjust/adjust/src/main/java/com/adjust/sdk/AdjustFactory.java b/Adjust/adjust/src/main/java/com/adjust/sdk/AdjustFactory.java index 929813c7b..070fb167f 100644 --- a/Adjust/adjust/src/main/java/com/adjust/sdk/AdjustFactory.java +++ b/Adjust/adjust/src/main/java/com/adjust/sdk/AdjustFactory.java @@ -4,8 +4,18 @@ import java.io.IOException; import java.net.URL; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; public class AdjustFactory { private static IPackageHandler packageHandler = null; @@ -23,6 +33,9 @@ public class AdjustFactory { private static BackoffStrategy sdkClickBackoffStrategy = null; private static BackoffStrategy packageHandlerBackoffStrategy = null; private static long maxDelayStart = -1; + private static String baseUrl = Constants.BASE_URL; + private static UtilNetworking.IConnectionOptions connectionOptions = null; + private static boolean tryInstallReferrer = true; public static class URLGetConnection { HttpsURLConnection httpsURLConnection; @@ -143,6 +156,24 @@ public static long getMaxDelayStart() { return maxDelayStart; } + public static String getBaseUrl() { + if (AdjustFactory.baseUrl == null) { + return Constants.BASE_URL; + } + return AdjustFactory.baseUrl; + } + + public static UtilNetworking.IConnectionOptions getConnectionOptions() { + if (connectionOptions == null) { + return new UtilNetworking.ConnectionOptions(); + } + return connectionOptions; + } + + public static boolean getTryInstallReferrer() { + return tryInstallReferrer; + } + public static void setPackageHandler(IPackageHandler packageHandler) { AdjustFactory.packageHandler = packageHandler; } @@ -194,4 +225,118 @@ public static void setHttpsURLConnection(HttpsURLConnection httpsURLConnection) public static void setSdkClickHandler(ISdkClickHandler sdkClickHandler) { AdjustFactory.sdkClickHandler = sdkClickHandler; } + + public static void setBaseUrl(String baseUrl) { + AdjustFactory.baseUrl = baseUrl; + } + + public static void useTestConnectionOptions() { + AdjustFactory.connectionOptions = new UtilNetworking.IConnectionOptions() { + @Override + public void applyConnectionOptions(HttpsURLConnection connection, String clientSdk) { + UtilNetworking.ConnectionOptions defaultConnectionOption = new UtilNetworking.ConnectionOptions(); + defaultConnectionOption.applyConnectionOptions(connection, clientSdk); + try { + SSLContext sc = SSLContext.getInstance("TLS"); + sc.init(null, new TrustManager[]{ + new X509TrustManager() { + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + getLogger().verbose("getAcceptedIssuers"); + return null; + } + public void checkClientTrusted( + X509Certificate[] certs, String authType) { + getLogger().verbose("checkClientTrusted "); + } + public void checkServerTrusted( + X509Certificate[] certs, String authType) throws CertificateException { + getLogger().verbose("checkServerTrusted "); + + String serverThumbprint = "7BCFF44099A35BC093BB48C5A6B9A516CDFDA0D1"; + X509Certificate certificate = certs[0]; + + MessageDigest md = null; + try { + md = MessageDigest.getInstance("SHA1"); + byte[] publicKey = md.digest(certificate.getEncoded()); + String hexString = byte2HexFormatted(publicKey); + + if (!hexString.equalsIgnoreCase(serverThumbprint)) { + throw new CertificateException(); + } + } catch (NoSuchAlgorithmException e) { + getLogger().error("testingMode error %s", e.getMessage()); + } catch (CertificateEncodingException e) { + getLogger().error("testingMode error %s", e.getMessage()); + } + } + } + }, new java.security.SecureRandom()); + connection.setSSLSocketFactory(sc.getSocketFactory()); + + connection.setHostnameVerifier(new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + getLogger().verbose("verify hostname "); + return true; + } + }); + } catch (Exception e) { + getLogger().error("testingMode error %s", e.getMessage()); + } + } + }; + + } + + public static void setTryInstallReferrer(boolean tryInstallReferrer) { + AdjustFactory.tryInstallReferrer = tryInstallReferrer; + } + + private static String byte2HexFormatted(byte[] arr) { + StringBuilder str = new StringBuilder(arr.length * 2); + + for (int i = 0; i < arr.length; i++) { + String h = Integer.toHexString(arr[i]); + int l = h.length(); + + if (l == 1) { + h = "0" + h; + } + + if (l > 2) { + h = h.substring(l - 2, l); + } + + str.append(h.toUpperCase()); + + // if (i < (arr.length - 1)) str.append(':'); + } + return str.toString(); + } + + public static void teardown(Context context) { + if(context != null) { + ActivityHandler.deleteState(context); + PackageHandler.deleteState(context); + } + packageHandler = null; + requestHandler = null; + attributionHandler = null; + activityHandler = null; + logger = null; + httpsURLConnection = null; + sdkClickHandler = null; + + timerInterval = -1; + timerStart = -1; + sessionInterval = -1; + subsessionInterval = -1; + sdkClickBackoffStrategy = null; + packageHandlerBackoffStrategy = null; + maxDelayStart = -1; + baseUrl = Constants.BASE_URL; + connectionOptions = null; + tryInstallReferrer = true; + } } diff --git a/Adjust/adjust/src/main/java/com/adjust/sdk/AdjustInstance.java b/Adjust/adjust/src/main/java/com/adjust/sdk/AdjustInstance.java index 599375a27..21f7b70a3 100644 --- a/Adjust/adjust/src/main/java/com/adjust/sdk/AdjustInstance.java +++ b/Adjust/adjust/src/main/java/com/adjust/sdk/AdjustInstance.java @@ -37,6 +37,7 @@ public class AdjustInstance { * Array of actions that were requested before SDK initialisation. */ private List preLaunchActionsArray; + private String basePath; /** * Called upon SDK initialisation. @@ -44,6 +45,16 @@ public class AdjustInstance { * @param adjustConfig AdjustConfig object used for SDK initialisation */ public void onCreate(final AdjustConfig adjustConfig) { + if (adjustConfig == null) { + AdjustFactory.getLogger().error("AdjustConfig missing"); + return; + } + + if (!adjustConfig.isValid()) { + AdjustFactory.getLogger().error("AdjustConfig not initialized correctly"); + return; + } + if (activityHandler != null) { AdjustFactory.getLogger().error("Adjust already initialized"); return; @@ -53,6 +64,7 @@ public void onCreate(final AdjustConfig adjustConfig) { adjustConfig.pushToken = pushToken; adjustConfig.startEnabled = startEnabled; adjustConfig.startOffline = startOffline; + adjustConfig.basePath = this.basePath; activityHandler = AdjustFactory.getActivityHandler(adjustConfig); @@ -320,15 +332,13 @@ public void run(final ActivityHandler activityHandler) { /** * Called to teardown SDK state. * Used only for Adjust tests, shouldn't be used in client apps. - * - * @param deleteState boolean indicating should internal Adjust files also be removed or not */ - public void teardown(final boolean deleteState) { + public void teardown() { if (!checkActivityHandler()) { return; } - activityHandler.teardown(deleteState); + activityHandler.teardown(); activityHandler = null; } @@ -488,4 +498,31 @@ public void run() { private boolean isInstanceEnabled() { return this.startEnabled == null || this.startEnabled; } + + public void setTestOptions(AdjustTestOptions testOptions) { + if (testOptions.basePath != null) { + this.basePath = testOptions.basePath; + } + if (testOptions.baseUrl != null) { + AdjustFactory.setBaseUrl(testOptions.baseUrl); + } + if (testOptions.useTestConnectionOptions != null && testOptions.useTestConnectionOptions.booleanValue()) { + AdjustFactory.useTestConnectionOptions(); + } + if (testOptions.timerIntervalInMilliseconds != null) { + AdjustFactory.setTimerInterval(testOptions.timerIntervalInMilliseconds); + } + if (testOptions.timerStartInMilliseconds != null) { + AdjustFactory.setTimerStart(testOptions.timerIntervalInMilliseconds); + } + if (testOptions.sessionIntervalInMilliseconds != null) { + AdjustFactory.setSessionInterval(testOptions.sessionIntervalInMilliseconds); + } + if (testOptions.subsessionIntervalInMilliseconds != null) { + AdjustFactory.setSubsessionInterval(testOptions.subsessionIntervalInMilliseconds); + } + if (testOptions.tryInstallReferrer != null) { + AdjustFactory.setTryInstallReferrer(testOptions.tryInstallReferrer); + } + } } diff --git a/Adjust/adjust/src/main/java/com/adjust/sdk/AdjustTestOptions.java b/Adjust/adjust/src/main/java/com/adjust/sdk/AdjustTestOptions.java new file mode 100644 index 000000000..0376f833e --- /dev/null +++ b/Adjust/adjust/src/main/java/com/adjust/sdk/AdjustTestOptions.java @@ -0,0 +1,20 @@ +package com.adjust.sdk; + +import android.content.Context; + +/** + * Created by nonelse on 08.01.2018 + */ + +public class AdjustTestOptions { + public Context context; + public String baseUrl; + public String basePath; + public Boolean useTestConnectionOptions; + public Long timerIntervalInMilliseconds; + public Long timerStartInMilliseconds; + public Long sessionIntervalInMilliseconds; + public Long subsessionIntervalInMilliseconds; + public Boolean teardown; + public Boolean tryInstallReferrer; +} diff --git a/Adjust/adjust/src/main/java/com/adjust/sdk/AttributionHandler.java b/Adjust/adjust/src/main/java/com/adjust/sdk/AttributionHandler.java index 3ef836531..a99ce2dae 100644 --- a/Adjust/adjust/src/main/java/com/adjust/sdk/AttributionHandler.java +++ b/Adjust/adjust/src/main/java/com/adjust/sdk/AttributionHandler.java @@ -20,6 +20,7 @@ public class AttributionHandler implements IAttributionHandler { private static final String ATTRIBUTION_TIMER_NAME = "Attribution timer"; private boolean paused; + private String basePath; @Override public void teardown() { @@ -55,6 +56,7 @@ public void run() { } }, ATTRIBUTION_TIMER_NAME); + basePath = activityHandler.getBasePath(); init(activityHandler, attributionPackage, startsSending); } @@ -223,7 +225,7 @@ private void sendAttributionRequestI() { logger.verbose("%s", attributionPackage.getExtendedString()); try { - ResponseData responseData = UtilNetworking.createGETHttpsURLConnection(attributionPackage); + ResponseData responseData = UtilNetworking.createGETHttpsURLConnection(attributionPackage, basePath); if (!(responseData instanceof AttributionResponseData)) { return; diff --git a/Adjust/adjust/src/main/java/com/adjust/sdk/Constants.java b/Adjust/adjust/src/main/java/com/adjust/sdk/Constants.java index 4c9c2ba3c..4fc89df99 100644 --- a/Adjust/adjust/src/main/java/com/adjust/sdk/Constants.java +++ b/Adjust/adjust/src/main/java/com/adjust/sdk/Constants.java @@ -29,7 +29,7 @@ public interface Constants { String BASE_URL = "https://app.adjust.com"; String SCHEME = "https"; String AUTHORITY = "app.adjust.com"; - String CLIENT_SDK = "android4.12.1"; + String CLIENT_SDK = "android4.12.2"; String LOGTAG = "Adjust"; String REFTAG = "reftag"; String INSTALL_REFERRER = "install_referrer"; diff --git a/Adjust/adjust/src/main/java/com/adjust/sdk/CustomScheduledExecutor.java b/Adjust/adjust/src/main/java/com/adjust/sdk/CustomScheduledExecutor.java index d05321677..1683efee3 100644 --- a/Adjust/adjust/src/main/java/com/adjust/sdk/CustomScheduledExecutor.java +++ b/Adjust/adjust/src/main/java/com/adjust/sdk/CustomScheduledExecutor.java @@ -8,25 +8,17 @@ import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.*; /** * Created by pfms on 05/08/2016. */ -public final class CustomScheduledExecutor { - private String source; - private ScheduledThreadPoolExecutor executor; - private final AtomicInteger threadCounter = new AtomicInteger(1); - - +public final class CustomScheduledExecutor extends ScheduledThreadPoolExecutor { public CustomScheduledExecutor(final String source, boolean doKeepAlive) { - this.source = source; - - executor = new ScheduledThreadPoolExecutor(1, // Single thread + super(1, new ThreadFactory() { // Creator of daemon threads @Override public Thread newThread(Runnable runnable) { - Thread thread = Executors.defaultThreadFactory().newThread(new RunnableWrapper(runnable)); + Thread thread = Executors.defaultThreadFactory().newThread(runnable); thread.setPriority(Thread.MIN_PRIORITY); thread.setName(Constants.THREAD_PREFIX + thread.getName() + "-" + source); @@ -35,69 +27,57 @@ public Thread newThread(Runnable runnable) { thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread th, Throwable tr) { - AdjustFactory.getLogger().error("Thread %s with error %s", th.getName(), tr.getMessage()); + AdjustFactory.getLogger().error("Thread [%s] with error [%s]", + th.getName(), tr.getMessage()); } }); - //AdjustFactory.getLogger().verbose("Thread %s created", thread.getName()); return thread; } }, new RejectedExecutionHandler() { // Logs rejected runnables rejected from the entering the pool - @Override - public void rejectedExecution(Runnable runnable, ThreadPoolExecutor executor) { - AdjustFactory.getLogger().warn("Runnable %s rejected from %s ", runnable.toString(), source); - } - } + @Override + public void rejectedExecution(Runnable runnable, ThreadPoolExecutor executor) { + AdjustFactory.getLogger().warn("Runnable [%s] rejected from [%s] ", + runnable.toString(), source); + } + } ); if (!doKeepAlive) { - executor.setKeepAliveTime(10L, TimeUnit.MILLISECONDS); - executor.allowCoreThreadTimeOut(true); + setKeepAliveTime(10L, TimeUnit.MILLISECONDS); + allowCoreThreadTimeOut(true); } } + @Override public Future submit(Runnable task) { - return executor.submit(task); - } - - public void shutdownNow() { - executor.shutdownNow(); + return super.submit(new RunnableWrapper(task)); } + @Override public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { -// AdjustFactory.getLogger().verbose("CustomScheduledExecutor scheduleWithFixedDelay from %s source, with %d delay and %d initial delay", -// source, delay, initialDelay); - return executor.scheduleWithFixedDelay(command, initialDelay, delay, unit); + return super.scheduleWithFixedDelay(new RunnableWrapper(command), initialDelay, delay, unit); } + @Override public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { -// AdjustFactory.getLogger().verbose("CustomScheduledExecutor schedule from %s source, with %d delay", source, delay); - return executor.schedule(command, delay, unit); + return super.schedule(new RunnableWrapper(command), delay, unit); } - private class RunnableWrapper implements Runnable { + private static class RunnableWrapper implements Runnable { private Runnable runnable; -// private long created; -// private int threadNumber; - public RunnableWrapper(Runnable runnable) { + RunnableWrapper(Runnable runnable) { this.runnable = runnable; -// created = System.currentTimeMillis(); -// threadNumber = threadCounter.getAndIncrement(); -// AdjustFactory.getLogger().verbose("RunnableWrapper %d from %s created at %d", threadNumber, source, created); } @Override public void run() { try { -// long before = System.currentTimeMillis(); -// AdjustFactory.getLogger().verbose("RunnableWrapper %d from %s source, before running at %d", threadNumber, source, before); runnable.run(); -// long after = System.currentTimeMillis(); -// AdjustFactory.getLogger().verbose("RunnableWrapper %d from %s source, after running at %d", threadNumber, source, after); - } catch (Throwable t) { - AdjustFactory.getLogger().error("Runnable error %s", t.getMessage()); + AdjustFactory.getLogger().error("Runnable error [%s] of type [%s]", + t.getMessage(), t.getClass().getCanonicalName()); } } } diff --git a/Adjust/adjust/src/main/java/com/adjust/sdk/IActivityHandler.java b/Adjust/adjust/src/main/java/com/adjust/sdk/IActivityHandler.java index 4b78fe6ef..ce227bf4d 100644 --- a/Adjust/adjust/src/main/java/com/adjust/sdk/IActivityHandler.java +++ b/Adjust/adjust/src/main/java/com/adjust/sdk/IActivityHandler.java @@ -55,7 +55,7 @@ public interface IActivityHandler { void resetSessionPartnerParameters(); - void teardown(boolean deleteState); + void teardown(); void setPushToken(String token, boolean preSaved); @@ -73,4 +73,5 @@ public interface IActivityHandler { SessionParameters getSessionParameters(); + String getBasePath(); } diff --git a/Adjust/adjust/src/main/java/com/adjust/sdk/IPackageHandler.java b/Adjust/adjust/src/main/java/com/adjust/sdk/IPackageHandler.java index f1304ed66..ab186b239 100644 --- a/Adjust/adjust/src/main/java/com/adjust/sdk/IPackageHandler.java +++ b/Adjust/adjust/src/main/java/com/adjust/sdk/IPackageHandler.java @@ -19,5 +19,7 @@ public interface IPackageHandler { void updatePackages(SessionParameters sessionParameters); - void teardown(boolean deleteState); + String getBasePath(); + + void teardown(); } diff --git a/Adjust/adjust/src/main/java/com/adjust/sdk/InstallReferrer.java b/Adjust/adjust/src/main/java/com/adjust/sdk/InstallReferrer.java index bdb756ba4..5351b9c4e 100644 --- a/Adjust/adjust/src/main/java/com/adjust/sdk/InstallReferrer.java +++ b/Adjust/adjust/src/main/java/com/adjust/sdk/InstallReferrer.java @@ -2,8 +2,11 @@ import android.content.Context; +import java.io.PrintWriter; +import java.io.StringWriter; import java.lang.ref.WeakReference; import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Proxy; @@ -20,7 +23,7 @@ public class InstallReferrer implements InvocationHandler { /** * Android install referrer library package name. */ - public static final String PACKAGE_BASE_NAME = "com.android.installreferrer."; + private static final String PACKAGE_BASE_NAME = "com.android.installreferrer."; /** * Play Store service is not connected now - potentially transient state. @@ -79,7 +82,7 @@ public class InstallReferrer implements InvocationHandler { /** * Lock. */ - private Object flagLock; + private final Object flagLock; /** * Timer which fires retry attempts. @@ -110,14 +113,15 @@ public void run() { } }, "InstallReferrer"); activityHandlerWeakRef = new WeakReference(activityHandler); - - startConnection(); } /** * Start connection with install referrer service. */ public void startConnection() { + if (!AdjustFactory.getTryInstallReferrer()) { + return; + } closeReferrerClient(); synchronized (flagLock) { @@ -127,10 +131,24 @@ public void startConnection() { } } + if (context == null) { + return; + } + this.referrerClient = createInstallReferrerClient(context); + if (this.referrerClient == null) { + return; + } Class listenerClass = getInstallReferrerStateListenerClass(); + if (listenerClass == null) { + return; + } + Object listenerProxy = createProxyInstallReferrerStateListener(listenerClass); + if (listenerProxy == null) { + return; + } startConnection(listenerClass, listenerProxy); } @@ -141,18 +159,22 @@ public void startConnection() { * @param context App context * @return Instance of InstallReferrerClient. Defaults to null if failed to create one. */ - public Object createInstallReferrerClient(final Context context) { - if (context == null) { - return null; - } + private Object createInstallReferrerClient(final Context context) { try { Object builder = Reflection.invokeStaticMethod(PACKAGE_BASE_NAME + "api.InstallReferrerClient", "newBuilder", new Class[]{Context.class}, context); return Reflection.invokeInstanceMethod(builder, "build", null); - } catch (Exception e) { - logger.warn("Couldn't create instance of referrer client (%s)", e.getMessage()); + } catch (ClassNotFoundException ex) { + logger.warn("InstallReferrer not integrated in project (%s) thrown by (%s)", + ex.getMessage(), + ex.getClass().getCanonicalName()); + } catch (Exception ex) { + logger.error("createInstallReferrerClient error (%s) from (%s)", + ex.getMessage(), + ex.getClass().getCanonicalName()); } + return null; } @@ -161,11 +183,13 @@ public Object createInstallReferrerClient(final Context context) { * * @return Class object for InstallReferrerStateListener class. */ - public Class getInstallReferrerStateListenerClass() { + private Class getInstallReferrerStateListenerClass() { try { return Class.forName(PACKAGE_BASE_NAME + "api.InstallReferrerStateListener"); - } catch (Exception e) { - logger.error("getInstallReferrerStateListenerClass error (%s)", e.getMessage()); + } catch (Exception ex) { + logger.error("getInstallReferrerStateListenerClass error (%s) from (%s)", + ex.getMessage(), + ex.getClass().getCanonicalName()); } return null; } @@ -176,15 +200,22 @@ public Class getInstallReferrerStateListenerClass() { * @param installReferrerStateListenerClass Class object * @return Instance of Class type object. */ - public Object createProxyInstallReferrerStateListener(final Class installReferrerStateListenerClass) { - if (installReferrerStateListenerClass == null) { - return null; + private Object createProxyInstallReferrerStateListener(final Class installReferrerStateListenerClass) { + Object proxyInstance = null; + + try { + proxyInstance = Proxy.newProxyInstance( + installReferrerStateListenerClass.getClassLoader(), + new Class[]{installReferrerStateListenerClass}, + this + ); + } catch (IllegalArgumentException ex) { + logger.error("InstallReferrer proxy violating parameter restrictions"); + } catch (NullPointerException ex) { + logger.error("Null argument passed to InstallReferrer proxy"); } - return Proxy.newProxyInstance( - installReferrerStateListenerClass.getClassLoader(), - new Class[]{installReferrerStateListenerClass}, - this - ); + + return proxyInstance; } /** @@ -193,18 +224,20 @@ public Object createProxyInstallReferrerStateListener(final Class installReferre * @param listenerClass Callback listener class type * @param listenerProxy Callback listener object instance */ - public void startConnection(final Class listenerClass, final Object listenerProxy) { - if (referrerClient == null) { - return; - } - if (listenerClass == null || listenerProxy == null) { - return; - } + private void startConnection(final Class listenerClass, final Object listenerProxy) { try { Reflection.invokeInstanceMethod(this.referrerClient, "startConnection", new Class[]{listenerClass}, listenerProxy); - } catch (Exception e) { - logger.error("startConnection error (%s)", e.getMessage()); + } catch (InvocationTargetException ex) { + // Check for an underlying root cause in the stack trace + if (Util.hasRootCause(ex)) { + logger.error("InstallReferrer encountered an InvocationTargetException %s", + Util.getRootCause(ex)); + } + } catch (Exception ex) { + logger.error("startConnection error (%s) thrown by (%s)", + ex.getMessage(), + ex.getClass().getCanonicalName()); } } @@ -213,22 +246,18 @@ public void startConnection(final Class listenerClass, final Object listenerProx */ @Override public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable { - try { - String methodName = method.getName(); - // Prints the method being invoked - logger.debug("InstallReferrer invoke method name: %s", methodName); - for (Object arg : args) { - logger.debug("InstallReferrer invoke arg: %s", arg); - } + String methodName = method.getName(); + // Prints the method being invoked + logger.debug("InstallReferrer invoke method name: %s", methodName); + for (Object arg : args) { + logger.debug("InstallReferrer invoke arg: %s", arg); + } - // if the method name equals some method's name then call your method - if (methodName.equals("onInstallReferrerSetupFinished")) { - onInstallReferrerSetupFinishedInt((Integer) args[0]); - } else if (methodName.equals("onInstallReferrerServiceDisconnected")) { - logger.debug("InstallReferrer onInstallReferrerServiceDisconnected"); - } - } catch (Exception e) { - logger.error("InstallReferrer invoke error (%s)", e.getMessage()); + // if the method name equals some method's name then call your method + if (methodName.equals("onInstallReferrerSetupFinished")) { + onInstallReferrerSetupFinishedInt((Integer) args[0]); + } else if (methodName.equals("onInstallReferrerServiceDisconnected")) { + logger.debug("InstallReferrer onInstallReferrerServiceDisconnected"); } return null; } @@ -238,7 +267,7 @@ public Object invoke(final Object proxy, final Method method, final Object[] arg * * @param responseCode Response code from install referrer service */ - public void onInstallReferrerSetupFinishedInt(final int responseCode) { + private void onInstallReferrerSetupFinishedInt(final int responseCode) { switch (responseCode) { case STATUS_OK: // Connection established @@ -259,8 +288,9 @@ public void onInstallReferrerSetupFinishedInt(final int responseCode) { synchronized (flagLock) { hasInstallReferrerBeenRead = true; } + closeReferrerClient(); } catch (Exception e) { - logger.debug("Couldn't get install referrer from client (%s). Retrying ...", e.getMessage()); + logger.warn("Couldn't get install referrer from client (%s). Retrying ...", e.getMessage()); retry(); } break; @@ -284,9 +314,9 @@ public void onInstallReferrerSetupFinishedInt(final int responseCode) { break; default: logger.debug("Unexpected response code of install referrer response: %d", responseCode); + closeReferrerClient(); break; } - closeReferrerClient(); } /** @@ -302,7 +332,9 @@ private Object getInstallReferrer() { return Reflection.invokeInstanceMethod( this.referrerClient, "getInstallReferrer", null); } catch (Exception e) { - logger.error("getInstallReferrer error (%s)", e.getMessage()); + logger.error("getInstallReferrer error (%s) thrown by (%s)", + e.getMessage(), + e.getClass().getCanonicalName()); } return null; } @@ -322,7 +354,9 @@ private String getStringInstallReferrer(final Object referrerDetails) { referrerDetails, "getInstallReferrer", null); return stringInstallReferrer; } catch (Exception e) { - logger.error("getStringInstallReferrer error (%s)", e.getMessage()); + logger.error("getStringInstallReferrer error (%s) thrown by (%s)", + e.getMessage(), + e.getClass().getCanonicalName()); } return null; } @@ -342,7 +376,9 @@ private long getReferrerClickTimestampSeconds(final Object referrerDetails) { referrerDetails, "getReferrerClickTimestampSeconds", null); return clickTime; } catch (Exception e) { - logger.error("getReferrerClickTimestampSeconds error (%s)", e.getMessage()); + logger.error("getReferrerClickTimestampSeconds error (%s) thrown by (%s)", + e.getMessage(), + e.getClass().getCanonicalName()); } return -1; } @@ -362,7 +398,9 @@ private long getInstallBeginTimestampSeconds(final Object referrerDetails) { referrerDetails, "getInstallBeginTimestampSeconds", null); return installBeginTime; } catch (Exception e) { - logger.error("getInstallBeginTimestampSeconds error (%s)", e.getMessage()); + logger.error("getInstallBeginTimestampSeconds error (%s) thrown by (%s)", + e.getMessage(), + e.getClass().getCanonicalName()); } return -1; } @@ -399,10 +437,13 @@ private void closeReferrerClient() { if (referrerClient == null) { return; } + try { Reflection.invokeInstanceMethod(referrerClient, "endConnection", null); } catch (Exception e) { - logger.error("closeReferrerClient error (%s)", e.getMessage()); + logger.error("closeReferrerClient error (%s) thrown by (%s)", + e.getMessage(), + e.getClass().getCanonicalName()); } referrerClient = null; } diff --git a/Adjust/adjust/src/main/java/com/adjust/sdk/PackageFactory.java b/Adjust/adjust/src/main/java/com/adjust/sdk/PackageFactory.java index 711a3da93..f45e1bb37 100644 --- a/Adjust/adjust/src/main/java/com/adjust/sdk/PackageFactory.java +++ b/Adjust/adjust/src/main/java/com/adjust/sdk/PackageFactory.java @@ -35,6 +35,13 @@ public static ActivityPackage buildReftagSdkClickPackage(final String rawReferre referrer = URLDecoder.decode(rawReferrer, ENCODING); } catch (UnsupportedEncodingException e) { referrer = MALFORMED; + AdjustFactory.getLogger().error("Referrer decoding failed due to UnsupportedEncodingException. Message: (%s)", e.getMessage()); + } catch (IllegalArgumentException e) { + referrer = MALFORMED; + AdjustFactory.getLogger().error("Referrer decoding failed due to IllegalArgumentException. Message: (%s)", e.getMessage()); + } catch (Exception e) { + referrer = MALFORMED; + AdjustFactory.getLogger().error("Referrer decoding failed. Message: (%s)", e.getMessage()); } AdjustFactory.getLogger().verbose("Referrer to parse (%s)", referrer); diff --git a/Adjust/adjust/src/main/java/com/adjust/sdk/PackageHandler.java b/Adjust/adjust/src/main/java/com/adjust/sdk/PackageHandler.java index 958bc9fcf..3fc57630e 100644 --- a/Adjust/adjust/src/main/java/com/adjust/sdk/PackageHandler.java +++ b/Adjust/adjust/src/main/java/com/adjust/sdk/PackageHandler.java @@ -35,9 +35,10 @@ public class PackageHandler implements IPackageHandler { private Context context; private ILogger logger; private BackoffStrategy backoffStrategy; + private String basePath; @Override - public void teardown(boolean deleteState) { + public void teardown() { logger.verbose("PackageHandler teardown"); if (scheduledExecutor != null) { try { @@ -53,9 +54,6 @@ public void teardown(boolean deleteState) { if (packageQueue != null) { packageQueue.clear(); } - if (deleteState && context != null) { - deletePackageQueue(context); - } scheduledExecutor = null; requestHandler = null; activityHandlerWeakRef = null; @@ -66,6 +64,10 @@ public void teardown(boolean deleteState) { backoffStrategy = null; } + static void deleteState(Context context) { + deletePackageQueue(context); + } + public PackageHandler(IActivityHandler activityHandler, Context context, boolean startsSending) { @@ -88,6 +90,7 @@ public void init(IActivityHandler activityHandler, Context context, boolean star this.activityHandlerWeakRef = new WeakReference(activityHandler); this.context = context; this.paused = !startsSending; + this.basePath = activityHandler.getBasePath(); } // add a package to the queue @@ -193,6 +196,12 @@ public void run() { } }); } + + @Override + public String getBasePath() { + return this.basePath; + } + // internal methods run in dedicated queue thread private void initI() { diff --git a/Adjust/adjust/src/main/java/com/adjust/sdk/RequestHandler.java b/Adjust/adjust/src/main/java/com/adjust/sdk/RequestHandler.java index 9c596b088..fbd79f3fb 100644 --- a/Adjust/adjust/src/main/java/com/adjust/sdk/RequestHandler.java +++ b/Adjust/adjust/src/main/java/com/adjust/sdk/RequestHandler.java @@ -20,11 +20,13 @@ public class RequestHandler implements IRequestHandler { private CustomScheduledExecutor scheduledExecutor; private WeakReference packageHandlerWeakRef; private ILogger logger; + private String basePath; public RequestHandler(IPackageHandler packageHandler) { this.logger = AdjustFactory.getLogger(); this.scheduledExecutor = new CustomScheduledExecutor("RequestHandler", false); init(packageHandler); + this.basePath = packageHandler.getBasePath(); } @Override @@ -59,7 +61,11 @@ public void teardown() { } private void sendI(ActivityPackage activityPackage, int queueSize) { - String targetURL = Constants.BASE_URL + activityPackage.getPath(); + String url = AdjustFactory.getBaseUrl(); + if (basePath != null) { + url += basePath; + } + String targetURL = url + activityPackage.getPath(); try { ResponseData responseData = UtilNetworking.createPOSTHttpsURLConnection(targetURL, activityPackage, queueSize); diff --git a/Adjust/adjust/src/main/java/com/adjust/sdk/SdkClickHandler.java b/Adjust/adjust/src/main/java/com/adjust/sdk/SdkClickHandler.java index de8537d21..56a5d7406 100644 --- a/Adjust/adjust/src/main/java/com/adjust/sdk/SdkClickHandler.java +++ b/Adjust/adjust/src/main/java/com/adjust/sdk/SdkClickHandler.java @@ -56,6 +56,11 @@ public class SdkClickHandler implements ISdkClickHandler { */ private BackoffStrategy backoffStrategy; + /** + * Base path. + */ + private String basePath; + /** * Sending queue. */ @@ -93,6 +98,7 @@ public void init(final IActivityHandler activityHandler, final boolean startsSen paused = !startsSending; packageQueue = new ArrayList(); activityHandlerWeakRef = new WeakReference(activityHandler); + basePath = activityHandler.getBasePath(); } /** @@ -303,7 +309,13 @@ private void sendSdkClickI(final ActivityPackage sdkClickPackage) { installReferrer = sdkClickPackage.getParameters().get("referrer"); } - String targetURL = Constants.BASE_URL + sdkClickPackage.getPath(); + String url = AdjustFactory.getBaseUrl(); + + if (basePath != null) { + url += basePath; + } + + String targetURL = url + sdkClickPackage.getPath(); try { SdkClickResponseData responseData = (SdkClickResponseData) UtilNetworking.createPOSTHttpsURLConnection( diff --git a/Adjust/adjust/src/main/java/com/adjust/sdk/Util.java b/Adjust/adjust/src/main/java/com/adjust/sdk/Util.java index 6b9da0ddf..bdb7733cd 100644 --- a/Adjust/adjust/src/main/java/com/adjust/sdk/Util.java +++ b/Adjust/adjust/src/main/java/com/adjust/sdk/Util.java @@ -30,9 +30,13 @@ import java.io.NotSerializableException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.InvocationTargetException; import java.math.BigInteger; import java.security.MessageDigest; import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.HashMap; @@ -55,7 +59,7 @@ public class Util { private static final String DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'Z"; private static final String fieldReadErrorMessage = "Unable to read '%s' field in migration device with message (%s)"; - public static final DecimalFormat SecondsDisplayFormat = new DecimalFormat("0.0"); + public static final DecimalFormat SecondsDisplayFormat = newLocalDecimalFormat(); public static final SimpleDateFormat dateFormatter = new SimpleDateFormat(DATE_FORMAT, Locale.US); private static ILogger getLogger() { @@ -66,6 +70,11 @@ protected static String createUuid() { return UUID.randomUUID().toString(); } + private static DecimalFormat newLocalDecimalFormat() { + DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.US); + return new DecimalFormat("0.0", symbols); + } + public static String quote(String string) { if (string == null) { return null; @@ -627,4 +636,28 @@ public static String getMnc(Context context) { public static String formatString(String format, Object... args) { return String.format(Locale.US, format, args); } + + public static boolean hasRootCause(Exception ex) { + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + ex.printStackTrace(pw); + String sStackTrace = sw.toString(); // stack trace as a string + + return sStackTrace.contains("Caused by:"); + } + + public static String getRootCause(Exception ex) { + if (!hasRootCause(ex)) { + return null; + } + + StringWriter sw = new StringWriter(); + PrintWriter pw = new PrintWriter(sw); + ex.printStackTrace(pw); + String sStackTrace = sw.toString(); // stack trace as a string + + int startOccuranceOfRootCause = sStackTrace.indexOf("Caused by:"); + int endOccuranceOfRootCause = sStackTrace.indexOf("\n", startOccuranceOfRootCause); + return sStackTrace.substring(startOccuranceOfRootCause, endOccuranceOfRootCause); + } } \ No newline at end of file diff --git a/Adjust/adjust/src/main/java/com/adjust/sdk/UtilNetworking.java b/Adjust/adjust/src/main/java/com/adjust/sdk/UtilNetworking.java index 83367d171..51e35824f 100644 --- a/Adjust/adjust/src/main/java/com/adjust/sdk/UtilNetworking.java +++ b/Adjust/adjust/src/main/java/com/adjust/sdk/UtilNetworking.java @@ -10,6 +10,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.UnsupportedEncodingException; +import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.util.HashMap; @@ -40,10 +41,12 @@ public static ResponseData createPOSTHttpsURLConnection(String urlString, Activi HttpsURLConnection connection = AdjustFactory.getHttpsURLConnection(url); Map parameters = new HashMap(activityPackage.getParameters()); + IConnectionOptions connectionOptions = AdjustFactory.getConnectionOptions(); + connectionOptions.applyConnectionOptions(connection, activityPackage.getClientSdk()); + String appSecret = extractAppSecret(parameters); String secretId = extractSecretId(parameters); - setDefaultHttpsUrlConnectionProperties(connection, activityPackage.getClientSdk()); String authorizationHeader = buildAuthorizationHeader(parameters, appSecret, secretId, activityPackage.getActivityKind().toString()); if (authorizationHeader != null) { connection.setRequestProperty("Authorization", authorizationHeader); @@ -72,23 +75,25 @@ public static ResponseData createPOSTHttpsURLConnection(String urlString, Activi } } - public static ResponseData createGETHttpsURLConnection(ActivityPackage activityPackage) throws Exception { + public static ResponseData createGETHttpsURLConnection(ActivityPackage activityPackage, String basePath) throws Exception { try { Map parameters = new HashMap(activityPackage.getParameters()); + String appSecret = extractAppSecret(parameters); String secretId = extractSecretId(parameters); - Uri uri = buildUri(activityPackage.getPath(), parameters); + Uri uri = buildUri(activityPackage.getPath(), parameters, basePath); URL url = new URL(uri.toString()); HttpsURLConnection connection = AdjustFactory.getHttpsURLConnection(url); + IConnectionOptions connectionOptions = AdjustFactory.getConnectionOptions(); + connectionOptions.applyConnectionOptions(connection, activityPackage.getClientSdk()); + String authorizationHeader = buildAuthorizationHeader(parameters, appSecret, secretId, activityPackage.getActivityKind().toString()); if (authorizationHeader != null) { connection.setRequestProperty("Authorization", authorizationHeader); } - setDefaultHttpsUrlConnectionProperties(connection, activityPackage.getClientSdk()); - connection.setRequestMethod("GET"); ResponseData responseData = readHttpResponse(connection, activityPackage); @@ -214,21 +219,29 @@ private static String getPostDataString(Map body, int queueSize) return result.toString(); } - private static void setDefaultHttpsUrlConnectionProperties(HttpsURLConnection connection, String clientSdk) { - connection.setRequestProperty("Client-SDK", clientSdk); - connection.setConnectTimeout(Constants.ONE_MINUTE); - connection.setReadTimeout(Constants.ONE_MINUTE); + private static Uri buildUri(String path, Map parameters, String basePath) { + Uri.Builder uriBuilder = new Uri.Builder(); - if (userAgent != null) { - connection.setRequestProperty("User-Agent", userAgent); - } - } + String scheme = Constants.SCHEME; + String authority = Constants.AUTHORITY; + String initialPath = ""; - private static Uri buildUri(String path, Map parameters) { - Uri.Builder uriBuilder = new Uri.Builder(); + try { + String url = AdjustFactory.getBaseUrl(); + if (basePath != null) { + url += basePath; + } + URL baseUrl = new URL(url); + scheme = baseUrl.getProtocol(); + authority = baseUrl.getAuthority(); + initialPath = baseUrl.getPath(); + } catch (MalformedURLException e) { + getLogger().error("Unable to parse endpoint (%s)", e.getMessage()); + } - uriBuilder.scheme(Constants.SCHEME); - uriBuilder.authority(Constants.AUTHORITY); + uriBuilder.scheme(scheme); + uriBuilder.encodedAuthority(authority); + uriBuilder.path(initialPath); uriBuilder.appendPath(path); for (Map.Entry entry : parameters.entrySet()) { @@ -350,4 +363,20 @@ private static String getValidIdentifier(final Map parameters) { return null; } + public interface IConnectionOptions { + void applyConnectionOptions(HttpsURLConnection connection, String clientSdk); + } + + static class ConnectionOptions implements IConnectionOptions { + @Override + public void applyConnectionOptions(HttpsURLConnection connection, String clientSdk) { + connection.setRequestProperty("Client-SDK", clientSdk); + connection.setConnectTimeout(Constants.ONE_MINUTE); + connection.setReadTimeout(Constants.ONE_MINUTE); + + if (userAgent != null) { + connection.setRequestProperty("User-Agent", userAgent); + } + } + } } diff --git a/Adjust/example/build.gradle b/Adjust/example/build.gradle index 02062f5a0..b8dfa472c 100644 --- a/Adjust/example/build.gradle +++ b/Adjust/example/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.application' android { compileSdkVersion 26 - buildToolsVersion '26.0.2' + buildToolsVersion '27.0.3' defaultConfig { applicationId "com.adjust.examples" @@ -21,13 +21,13 @@ android { dependencies { implementation 'com.android.support:appcompat-v7:26.1.0' - implementation 'com.google.android.gms:play-services-analytics:11.4.2' + implementation 'com.google.android.gms:play-services-analytics:11.8.0' // imported module implementation project(":adjust") // running mvn package //compile fileTree(dir: '../target', include: ['*.jar']) // using maven repository - //compile 'com.adjust.sdk:adjust-android:4.12.1' + //compile 'com.adjust.sdk:adjust-android:4.12.2' debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.4' releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4' diff --git a/Adjust/gradle/wrapper/gradle-wrapper.properties b/Adjust/gradle/wrapper/gradle-wrapper.properties index 2e23b91cb..1d8104d0e 100644 --- a/Adjust/gradle/wrapper/gradle-wrapper.properties +++ b/Adjust/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Nov 02 14:01:25 CET 2017 +#Mon Feb 12 10:45:26 CET 2018 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-4.5-all.zip diff --git a/Adjust/pom.xml b/Adjust/pom.xml index a053727d0..f8c14ee17 100644 --- a/Adjust/pom.xml +++ b/Adjust/pom.xml @@ -5,7 +5,7 @@ 4.0.0 adjust-android com.adjust.sdk - 4.12.1 + 4.12.2 jar Adjust Android SDK https://github.com/adjust/android_sdk diff --git a/Adjust/pom_criteo.xml b/Adjust/pom_criteo.xml index 12e20c400..208d25da1 100644 --- a/Adjust/pom_criteo.xml +++ b/Adjust/pom_criteo.xml @@ -5,7 +5,7 @@ 4.0.0 adjust-android-criteo com.adjust.sdk - 4.12.1 + 4.12.2 jar Adjust Android SDK https://github.com/adjust/android_sdk diff --git a/Adjust/pom_sociomantic.xml b/Adjust/pom_sociomantic.xml index b7f5aa0ca..09cbd13ef 100644 --- a/Adjust/pom_sociomantic.xml +++ b/Adjust/pom_sociomantic.xml @@ -5,7 +5,7 @@ 4.0.0 adjust-android-sociomantic com.adjust.sdk - 4.12.1 + 4.12.2 jar Adjust Android SDK https://github.com/adjust/android_sdk diff --git a/Adjust/pom_trademob.xml b/Adjust/pom_trademob.xml index 0eed38445..d71dd9300 100644 --- a/Adjust/pom_trademob.xml +++ b/Adjust/pom_trademob.xml @@ -5,7 +5,7 @@ 4.0.0 adjust-android-trademob com.adjust.sdk - 4.12.1 + 4.12.2 jar Adjust Android SDK https://github.com/adjust/android_sdk diff --git a/Adjust/settings.gradle b/Adjust/settings.gradle index ab8c3e3e4..1cb0de5c5 100644 --- a/Adjust/settings.gradle +++ b/Adjust/settings.gradle @@ -1 +1 @@ -include ':adjust', ':test', ':example' +include ':adjust', ':test', ':example', ':testlibrary', ':testapp' diff --git a/Adjust/test/build.gradle b/Adjust/test/build.gradle index 2570c8409..7dba4f033 100644 --- a/Adjust/test/build.gradle +++ b/Adjust/test/build.gradle @@ -2,7 +2,7 @@ apply plugin: 'com.android.library' android { compileSdkVersion 25 - buildToolsVersion '26.0.2' + buildToolsVersion '27.0.3' defaultConfig { minSdkVersion 9 diff --git a/Adjust/test/src/androidTest/java/com/adjust/sdk/TestActivityPackage.java b/Adjust/test/src/androidTest/java/com/adjust/sdk/TestActivityPackage.java index 4d5d18300..1256a9061 100644 --- a/Adjust/test/src/androidTest/java/com/adjust/sdk/TestActivityPackage.java +++ b/Adjust/test/src/androidTest/java/com/adjust/sdk/TestActivityPackage.java @@ -53,7 +53,7 @@ public TestActivityPackage(ActivityPackage activityPackage) { // default values appToken = "123456789012"; environment = "sandbox"; - clientSdk = "android4.12.1"; + clientSdk = "android4.12.2"; suffix = ""; attribution = new AdjustAttribution(); playServices = true; diff --git a/Adjust/testapp/.gitignore b/Adjust/testapp/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/Adjust/testapp/.gitignore @@ -0,0 +1 @@ +/build diff --git a/Adjust/testapp/build.gradle b/Adjust/testapp/build.gradle new file mode 100644 index 000000000..f78ff29ea --- /dev/null +++ b/Adjust/testapp/build.gradle @@ -0,0 +1,23 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 25 + buildToolsVersion '27.0.3' + + defaultConfig { + applicationId "com.adjust.testapp" + minSdkVersion 9 + targetSdkVersion 25 + versionCode 1 + versionName "1.0" + } +} + +dependencies { + implementation project(":testlibrary") + implementation project(":adjust") + + implementation 'com.android.support:appcompat-v7:25.4.0' + implementation 'com.android.support.constraint:constraint-layout:1.0.2' + implementation 'com.android.installreferrer:installreferrer:1.0' +} diff --git a/Adjust/testapp/proguard-rules.pro b/Adjust/testapp/proguard-rules.pro new file mode 100644 index 000000000..f1b424510 --- /dev/null +++ b/Adjust/testapp/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/Adjust/testapp/src/main/AndroidManifest.xml b/Adjust/testapp/src/main/AndroidManifest.xml new file mode 100644 index 000000000..df3f9c9b8 --- /dev/null +++ b/Adjust/testapp/src/main/AndroidManifest.xml @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Adjust/testapp/src/main/java/com/adjust/testapp/AdjustCommandExecutor.java b/Adjust/testapp/src/main/java/com/adjust/testapp/AdjustCommandExecutor.java new file mode 100644 index 000000000..aa3ed77de --- /dev/null +++ b/Adjust/testapp/src/main/java/com/adjust/testapp/AdjustCommandExecutor.java @@ -0,0 +1,589 @@ +package com.adjust.testapp; + +import android.content.Context; +import android.net.Uri; +import android.util.Log; +import android.util.SparseArray; + +import com.adjust.sdk.Adjust; +import com.adjust.sdk.AdjustAttribution; +import com.adjust.sdk.AdjustConfig; +import com.adjust.sdk.AdjustEvent; +import com.adjust.sdk.AdjustEventFailure; +import com.adjust.sdk.AdjustEventSuccess; +import com.adjust.sdk.AdjustSessionFailure; +import com.adjust.sdk.AdjustSessionSuccess; +import com.adjust.sdk.AdjustTestOptions; +import com.adjust.sdk.LogLevel; +import com.adjust.sdk.OnAttributionChangedListener; +import com.adjust.sdk.OnDeeplinkResponseListener; +import com.adjust.sdk.OnEventTrackingFailedListener; +import com.adjust.sdk.OnEventTrackingSucceededListener; +import com.adjust.sdk.OnSessionTrackingFailedListener; +import com.adjust.sdk.OnSessionTrackingSucceededListener; + +import java.util.List; + +import static com.adjust.testapp.MainActivity.baseUrl; + +/** + * Created by nonelse on 10.03.17. + */ + +public class AdjustCommandExecutor { + private static final String TAG = "AdjustCommandExecutor"; + private Context context; + private String basePath; + private SparseArray savedEvents = new SparseArray<>(); + private SparseArray savedConfigs = new SparseArray<>(); + private Command command; + + public AdjustCommandExecutor(Context context) { + this.context = context; + } + + public void executeCommand(Command command) { + this.command = command; + switch (command.methodName) { + // case "factory": factory(); break; + case "testOptions": testOptions(); break; + case "config": config(); break; + case "start": start(); break; + case "event": event(); break; + case "trackEvent": trackEvent(); break; + case "resume": resume(); break; + case "pause": pause(); break; + case "setEnabled": setEnabled(); break; + case "setReferrer": setReferrer(); break; + case "setOfflineMode": setOfflineMode(); break; + case "sendFirstPackages": sendFirstPackages(); break; + case "addSessionCallbackParameter": addSessionCallbackParameter(); break; + case "addSessionPartnerParameter": addSessionPartnerParameter(); break; + case "removeSessionCallbackParameter": removeSessionCallbackParameter(); break; + case "removeSessionPartnerParameter": removeSessionPartnerParameter(); break; + case "resetSessionCallbackParameters": resetSessionCallbackParameters(); break; + case "resetSessionPartnerParameters": resetSessionPartnerParameters(); break; + case "setPushToken": setPushToken(); break; + // case "teardown": teardown(); break; + case "openDeeplink": openDeeplink(); break; + case "sendReferrer": sendReferrer(); break; + //case "testBegin": testBegin(); break; + // case "testEnd": testEnd(); break; + } + } +/* + private void factory() { + if (command.containsParameter("basePath")) { + this.basePath = command.getFirstParameterValue("basePath"); + } + if (command.containsParameter("timerInterval")) { + long timerInterval = Long.parseLong(command.getFirstParameterValue("timerInterval")); + AdjustFactory.setTimerInterval(timerInterval); + } + if (command.containsParameter("timerStart")) { + long timerStart = Long.parseLong(command.getFirstParameterValue("timerStart")); + AdjustFactory.setTimerStart(timerStart); + } + if (command.containsParameter("sessionInterval")) { + long sessionInterval = Long.parseLong(command.getFirstParameterValue("sessionInterval")); + AdjustFactory.setSessionInterval(sessionInterval); + } + if (command.containsParameter("subsessionInterval")) { + long subsessionInterval = Long.parseLong(command.getFirstParameterValue("subsessionInterval")); + AdjustFactory.setSubsessionInterval(subsessionInterval); + } + } +*/ + + private void testOptions() { + AdjustTestOptions testOptions = new AdjustTestOptions(); + testOptions.baseUrl = baseUrl; + if (command.containsParameter("basePath")) { + basePath = command.getFirstParameterValue("basePath"); + } + if (command.containsParameter("timerInterval")) { + long timerInterval = Long.parseLong(command.getFirstParameterValue("timerInterval")); + testOptions.timerIntervalInMilliseconds = timerInterval; + } + if (command.containsParameter("timerStart")) { + long timerStart = Long.parseLong(command.getFirstParameterValue("timerStart")); + testOptions.timerStartInMilliseconds = timerStart; + } + if (command.containsParameter("sessionInterval")) { + long sessionInterval = Long.parseLong(command.getFirstParameterValue("sessionInterval")); + testOptions.sessionIntervalInMilliseconds = sessionInterval; + } + if (command.containsParameter("subsessionInterval")) { + long subsessionInterval = Long.parseLong(command.getFirstParameterValue("subsessionInterval")); + testOptions.subsessionIntervalInMilliseconds = subsessionInterval; + } + if (command.containsParameter("tryInstallReferrer")) { + String tryInstallReferrerString = command.getFirstParameterValue("tryInstallReferrer"); + Boolean tryInstallReferrerBoolean = Util.strictParseStringToBoolean(tryInstallReferrerString); + if (tryInstallReferrerBoolean != null) { + testOptions.tryInstallReferrer = tryInstallReferrerBoolean; + } + } + if (command.containsParameter("teardown")) { + List teardownOptions = command.parameters.get("teardown"); + for (String teardownOption : teardownOptions) { + if (teardownOption.equals("resetSdk")) { + testOptions.teardown = true; + testOptions.basePath = basePath; + testOptions.useTestConnectionOptions = true; + testOptions.tryInstallReferrer = false; + } + if (teardownOption.equals("deleteState")) { + testOptions.context = this.context; + } + if (teardownOption.equals("resetTest")) { + savedEvents.clear(); + savedConfigs.clear(); + testOptions.timerIntervalInMilliseconds = (long) -1; + testOptions.timerStartInMilliseconds = (long) -1; + testOptions.sessionIntervalInMilliseconds = (long) -1; + testOptions.subsessionIntervalInMilliseconds = (long) -1; + } + if (teardownOption.equals("sdk")) { + testOptions.teardown = true; + testOptions.basePath = null; + testOptions.useTestConnectionOptions = false; + } + if (teardownOption.equals("test")) { + savedEvents = null; + savedConfigs = null; + testOptions.timerIntervalInMilliseconds = (long) -1; + testOptions.timerStartInMilliseconds = (long) -1; + testOptions.sessionIntervalInMilliseconds = (long) -1; + testOptions.subsessionIntervalInMilliseconds = (long) -1; + } + } + } + Adjust.setTestOptions(testOptions); + } + + private void config() { + int configNumber = 0; + if (command.parameters.containsKey("configName")) { + String configName = command.getFirstParameterValue("configName"); + configNumber = Integer.parseInt(configName.substring(configName.length() - 1)); + } + + AdjustConfig adjustConfig; + + if (savedConfigs.indexOfKey(configNumber) >= 0) { + adjustConfig = savedConfigs.get(configNumber); + } else { + String environment = command.getFirstParameterValue("environment"); + String appToken = command.getFirstParameterValue("appToken"); + Context context = this.context; + if ("null".equalsIgnoreCase(command.getFirstParameterValue("context"))) { + context = null; + } + adjustConfig = new AdjustConfig(context, appToken, environment); +// String logLevel = command.getFirstParameterValue("logLevel"); +// adjustConfig.setLogLevel(LogLevel.valueOf(logLevel)); + adjustConfig.setLogLevel(LogLevel.VERBOSE); + + savedConfigs.put(configNumber, adjustConfig); + } + + if (command.containsParameter("logLevel")) { + String logLevelS = command.getFirstParameterValue("logLevel"); + LogLevel logLevel = null; + switch (logLevelS) { + case "verbose": logLevel = LogLevel.VERBOSE; + break; + case "debug": logLevel = LogLevel.DEBUG; + break; + case "info": logLevel = LogLevel.INFO; + break; + case "warn": logLevel = LogLevel.WARN; + break; + case "error": logLevel = LogLevel.ERROR; + break; + case "assert": logLevel = LogLevel.ASSERT; + break; + case "suppress": logLevel = LogLevel.SUPRESS; + break; + } + Log.d("TestApp", logLevel.toString()); + adjustConfig.setLogLevel(logLevel); + } + + if (command.containsParameter("sdkPrefix")) { + String sdkPrefix = command.getFirstParameterValue("sdkPrefix"); + adjustConfig.setSdkPrefix(sdkPrefix); + } + + if (command.containsParameter("defaultTracker")) { + String defaultTracker = command.getFirstParameterValue("defaultTracker"); + adjustConfig.setDefaultTracker(defaultTracker); + } + +// if (command.containsParameter("externalDeviceId")) { +// String externalDeviceId = command.getFirstParameterValue("externalDeviceId"); +// adjustConfig.setExternalDeviceId(externalDeviceId); +// } + + + if (command.parameters.containsKey("appSecret")) { + List appSecretArray = command.parameters.get("appSecret"); + try { + long secretId = Long.parseLong(appSecretArray.get(0)); + long info1 = Long.parseLong(appSecretArray.get(1)); + long info2 = Long.parseLong(appSecretArray.get(2)); + long info3 = Long.parseLong(appSecretArray.get(3)); + long info4 = Long.parseLong(appSecretArray.get(4)); + adjustConfig.setAppSecret(secretId, info1, info2, info3, info4); + } catch (Exception ignored) { + + } + } + + if (command.containsParameter("delayStart")) { + String delayStartS = command.getFirstParameterValue("delayStart"); + double delayStart = Double.parseDouble(delayStartS); + adjustConfig.setDelayStart(delayStart); + } + + if (command.containsParameter("deviceKnown")) { + String deviceKnownS = command.getFirstParameterValue("deviceKnown"); + boolean deviceKnown = "true".equals(deviceKnownS); + adjustConfig.setDeviceKnown(deviceKnown); + } + + if (command.containsParameter("eventBufferingEnabled")) { + String eventBufferingEnabledS = command.getFirstParameterValue("eventBufferingEnabled"); + boolean eventBufferingEnabled = "true".equals(eventBufferingEnabledS); + adjustConfig.setEventBufferingEnabled(eventBufferingEnabled); + } + + if (command.containsParameter("sendInBackground")) { + String sendInBackgroundS = command.getFirstParameterValue("sendInBackground"); + boolean sendInBackground = "true".equals(sendInBackgroundS); + adjustConfig.setSendInBackground(sendInBackground); + } + + if (command.containsParameter("userAgent")) { + String userAgent = command.getFirstParameterValue("userAgent"); + adjustConfig.setUserAgent(userAgent); + } + + if(command.containsParameter("deferredDeeplinkCallback")) { + adjustConfig.setOnDeeplinkResponseListener(new OnDeeplinkResponseListener() { + @Override + public boolean launchReceivedDeeplink(Uri deeplink) { + if (deeplink == null) { + Log.d("TestApp", "Deeplink Response, uri = null"); + return false; + } + + Log.d("TestApp", "Deeplink Response, uri = " + deeplink.toString()); + + return deeplink.toString().startsWith("adjusttest"); + } + }); + } + + if (command.containsParameter("attributionCallbackSendAll")) { + final String localBasePath = basePath; + adjustConfig.setOnAttributionChangedListener(new OnAttributionChangedListener() { + @Override + public void onAttributionChanged(AdjustAttribution attribution) { + Log.d("TestApp", "attribution = " + attribution.toString()); + + MainActivity.testLibrary.addInfoToSend("trackerToken", attribution.trackerToken); + MainActivity.testLibrary.addInfoToSend("trackerName", attribution.trackerName); + MainActivity.testLibrary.addInfoToSend("network", attribution.network); + MainActivity.testLibrary.addInfoToSend("campaign", attribution.campaign); + MainActivity.testLibrary.addInfoToSend("adgroup", attribution.adgroup); + MainActivity.testLibrary.addInfoToSend("creative", attribution.creative); + MainActivity.testLibrary.addInfoToSend("clickLabel", attribution.clickLabel); + MainActivity.testLibrary.addInfoToSend("adid", attribution.adid); + MainActivity.testLibrary.sendInfoToServer(localBasePath); + } + }); + } + + if (command.containsParameter("sessionCallbackSendSuccess")) { + final String localBasePath = basePath; + adjustConfig.setOnSessionTrackingSucceededListener(new OnSessionTrackingSucceededListener() { + @Override + public void onFinishedSessionTrackingSucceeded(AdjustSessionSuccess sessionSuccessResponseData) { + Log.d("TestApp", "session_success = " + sessionSuccessResponseData.toString()); + + MainActivity.testLibrary.addInfoToSend("message", sessionSuccessResponseData.message); + MainActivity.testLibrary.addInfoToSend("timestamp", sessionSuccessResponseData.timestamp); + MainActivity.testLibrary.addInfoToSend("adid", sessionSuccessResponseData.adid); + if (sessionSuccessResponseData.jsonResponse != null) { + MainActivity.testLibrary.addInfoToSend("jsonResponse", sessionSuccessResponseData.jsonResponse.toString()); + } + MainActivity.testLibrary.sendInfoToServer(localBasePath); + } + }); + } + + if (command.containsParameter("sessionCallbackSendFailure")) { + final String localBasePath = basePath; + adjustConfig.setOnSessionTrackingFailedListener(new OnSessionTrackingFailedListener() { + @Override + public void onFinishedSessionTrackingFailed(AdjustSessionFailure sessionFailureResponseData) { + Log.d("TestApp", "session_fail = " + sessionFailureResponseData.toString()); + + MainActivity.testLibrary.addInfoToSend("message", sessionFailureResponseData.message); + MainActivity.testLibrary.addInfoToSend("timestamp", sessionFailureResponseData.timestamp); + MainActivity.testLibrary.addInfoToSend("adid", sessionFailureResponseData.adid); + MainActivity.testLibrary.addInfoToSend("willRetry", String.valueOf(sessionFailureResponseData.willRetry)); + if (sessionFailureResponseData.jsonResponse != null) { + MainActivity.testLibrary.addInfoToSend("jsonResponse", sessionFailureResponseData.jsonResponse.toString()); + } + MainActivity.testLibrary.sendInfoToServer(localBasePath); + } + }); + } + + if (command.containsParameter("eventCallbackSendSuccess")) { + final String localBasePath = basePath; + adjustConfig.setOnEventTrackingSucceededListener(new OnEventTrackingSucceededListener() { + @Override + public void onFinishedEventTrackingSucceeded(AdjustEventSuccess eventSuccessResponseData) { + Log.d("TestApp", "event_success = " + eventSuccessResponseData.toString()); + + MainActivity.testLibrary.addInfoToSend("message", eventSuccessResponseData.message); + MainActivity.testLibrary.addInfoToSend("timestamp", eventSuccessResponseData.timestamp); + MainActivity.testLibrary.addInfoToSend("adid", eventSuccessResponseData.adid); + MainActivity.testLibrary.addInfoToSend("eventToken", eventSuccessResponseData.eventToken); + if (eventSuccessResponseData.jsonResponse != null ) { + MainActivity.testLibrary.addInfoToSend("jsonResponse", eventSuccessResponseData.jsonResponse.toString()); + } + MainActivity.testLibrary.sendInfoToServer(localBasePath); + } + }); + } + + if (command.containsParameter("eventCallbackSendFailure")) { + final String localBasePath = basePath; + adjustConfig.setOnEventTrackingFailedListener(new OnEventTrackingFailedListener() { + @Override + public void onFinishedEventTrackingFailed(AdjustEventFailure eventFailureResponseData) { + Log.d("TestApp", "event_fail = " + eventFailureResponseData.toString()); + + MainActivity.testLibrary.addInfoToSend("message", eventFailureResponseData.message); + MainActivity.testLibrary.addInfoToSend("timestamp", eventFailureResponseData.timestamp); + MainActivity.testLibrary.addInfoToSend("adid", eventFailureResponseData.adid); + MainActivity.testLibrary.addInfoToSend("eventToken", eventFailureResponseData.eventToken); + MainActivity.testLibrary.addInfoToSend("willRetry", String.valueOf(eventFailureResponseData.willRetry)); + if (eventFailureResponseData.jsonResponse != null) { + MainActivity.testLibrary.addInfoToSend("jsonResponse", eventFailureResponseData.jsonResponse.toString()); + } + MainActivity.testLibrary.sendInfoToServer(localBasePath); + } + }); + } + } + + private void start() { + config(); + int configNumber = 0; + if (command.parameters.containsKey("configName")) { + String configName = command.getFirstParameterValue("configName"); + configNumber = Integer.parseInt(configName.substring(configName.length() - 1)); + } + + AdjustConfig adjustConfig = savedConfigs.get(configNumber); + + //adjustConfig.setBasePath(basePath); + Adjust.onCreate(adjustConfig); + + this.savedConfigs.remove(0); + } + + private void event() throws NullPointerException { + int eventNumber = 0; + if (command.parameters.containsKey("eventName")) { + String eventName = command.getFirstParameterValue("eventName"); + eventNumber = Integer.parseInt(eventName.substring(eventName.length() - 1)); + } + + AdjustEvent adjustEvent; + if (savedEvents.indexOfKey(eventNumber) >= 0) { + adjustEvent = savedEvents.get(eventNumber); + } else { + String eventToken = command.getFirstParameterValue("eventToken"); + adjustEvent = new AdjustEvent(eventToken); + savedEvents.put(eventNumber, adjustEvent); + } + + if (command.parameters.containsKey("revenue")) { + List revenueParams = command.parameters.get("revenue"); + String currency = revenueParams.get(0); + double revenue = Double.parseDouble(revenueParams.get(1)); + adjustEvent.setRevenue(revenue, currency); + } + + if (command.parameters.containsKey("callbackParams")) { + List callbackParams = command.parameters.get("callbackParams"); + for (int i = 0; i < callbackParams.size(); i = i + 2) { + String key = callbackParams.get(i); + String value = callbackParams.get(i + 1); + adjustEvent.addCallbackParameter(key, value); + } + } + if (command.parameters.containsKey("partnerParams")) { + List partnerParams = command.parameters.get("partnerParams"); + for (int i = 0; i < partnerParams.size(); i = i + 2) { + String key = partnerParams.get(i); + String value = partnerParams.get(i + 1); + adjustEvent.addPartnerParameter(key, value); + } + } + if (command.parameters.containsKey("orderId")) { + String orderId = command.getFirstParameterValue("orderId"); + adjustEvent.setOrderId(orderId); + } + +// Adjust.trackEvent(adjustEvent); + } + + private void trackEvent() { + event(); + int eventNumber = 0; + if (command.parameters.containsKey("eventName")) { + String eventName = command.getFirstParameterValue("eventName"); + eventNumber = Integer.parseInt(eventName.substring(eventName.length() - 1)); + } + + AdjustEvent adjustEvent = savedEvents.get(eventNumber); + Adjust.trackEvent(adjustEvent); + + this.savedEvents.remove(0); + } + + private void setReferrer() { + String referrer = command.getFirstParameterValue("referrer"); + Adjust.setReferrer(referrer, this.context); + } + + private void pause() { + Adjust.onPause(); + } + + private void resume() { + Adjust.onResume(); + } + + private void setEnabled() { + Boolean enabled = Boolean.valueOf(command.getFirstParameterValue("enabled")); + Adjust.setEnabled(enabled); + } + + private void setOfflineMode() { + Boolean enabled = Boolean.valueOf(command.getFirstParameterValue("enabled")); + Adjust.setOfflineMode(enabled); + } + + private void sendFirstPackages() { + Adjust.sendFirstPackages(); + } + + private void addSessionCallbackParameter() { + if (command.containsParameter("KeyValue")) { + List keyValuePairs = command.parameters.get("KeyValue"); + for (int i = 0; i keyValuePairs = command.parameters.get("KeyValue"); + for (int i = 0; i keys = command.parameters.get("key"); + for (int i = 0; i keys = command.parameters.get("key"); + for (int i = 0; i(); + savedConfigs = new HashMap(); + } + + private void testEnd() { + AdjustFactory.teardown(this.context, true); + } + */ +} diff --git a/Adjust/testapp/src/main/java/com/adjust/testapp/Command.java b/Adjust/testapp/src/main/java/com/adjust/testapp/Command.java new file mode 100644 index 000000000..e9ed2b688 --- /dev/null +++ b/Adjust/testapp/src/main/java/com/adjust/testapp/Command.java @@ -0,0 +1,32 @@ +package com.adjust.testapp; + +import java.util.List; +import java.util.Map; + +/** + * Created by nonelse on 10.03.17. + */ + +public class Command { + String className; + String methodName; + Map> parameters; + + public Command(String className, String methodName, Map> parameters) { + this.className = className; + this.methodName = methodName; + this.parameters = parameters; + } + + public String getFirstParameterValue(String parameterKey) { + List parameterValues = this.parameters.get(parameterKey); + if (parameterValues == null || parameterValues.size() == 0) { + return null; + } + return parameterValues.get(0); + } + + public boolean containsParameter(String parameterKey) { + return this.parameters.get(parameterKey) != null; + } +} diff --git a/Adjust/testapp/src/main/java/com/adjust/testapp/CommandListener.java b/Adjust/testapp/src/main/java/com/adjust/testapp/CommandListener.java new file mode 100644 index 000000000..9290145b9 --- /dev/null +++ b/Adjust/testapp/src/main/java/com/adjust/testapp/CommandListener.java @@ -0,0 +1,44 @@ +package com.adjust.testapp; + +import android.content.Context; +import android.util.Log; + +import com.adjust.testlibrary.ICommandListener; + +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +/** + * Created by nonelse on 09.03.17. + */ + +public class CommandListener implements ICommandListener { + AdjustCommandExecutor adjustCommandExecutor; + + public CommandListener(Context context) { + adjustCommandExecutor = new AdjustCommandExecutor(context); + } + + @Override + public void executeCommand(String className, String methodName, Map> parameters) { + switch (className) { + case "Adjust": + adjustCommandExecutor.executeCommand(new Command(className, methodName, parameters)); + break; + default: + debug("Could not find %s class to execute", className); + break; + } + } + + static void debug(String message, Object... parameters) { + try { + Log.d("TestApp", String.format(Locale.US, message, parameters)); + } catch (Exception e) { + Log.e("TestApp", String.format(Locale.US, "Error formating log message: %s, with params: %s" + , message, Arrays.toString(parameters))); + } + } +} diff --git a/Adjust/testapp/src/main/java/com/adjust/testapp/MainActivity.java b/Adjust/testapp/src/main/java/com/adjust/testapp/MainActivity.java new file mode 100644 index 000000000..5338971f2 --- /dev/null +++ b/Adjust/testapp/src/main/java/com/adjust/testapp/MainActivity.java @@ -0,0 +1,39 @@ +package com.adjust.testapp; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.support.v7.app.AppCompatActivity; + +import com.adjust.sdk.Adjust; +import com.adjust.testlibrary.TestLibrary; + +public class MainActivity extends AppCompatActivity { + public static TestLibrary testLibrary; + public static final String baseUrl = "https://10.0.2.2:8443"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + // check if deferred deeplink was received + Intent intent = getIntent(); + Uri deeplinkData = intent.getData(); + if (deeplinkData != null) { + Adjust.appWillOpenUrl(deeplinkData); + return; + } + + testLibrary = new TestLibrary(baseUrl, new CommandListener(this.getApplicationContext())); +// testLibrary.doNotExitAfterEnd(); + startTestSession(); + } + + private void startTestSession() { + //testLibrary.addTestDirectory("current/sdkInfo"); + //testLibrary.addTest("current/appSecret/Test_AppSecret_no_secret"); + + testLibrary.startTestSession("android4.12.2"); + } +} diff --git a/Adjust/testapp/src/main/java/com/adjust/testapp/Util.java b/Adjust/testapp/src/main/java/com/adjust/testapp/Util.java new file mode 100644 index 000000000..e97628c5f --- /dev/null +++ b/Adjust/testapp/src/main/java/com/adjust/testapp/Util.java @@ -0,0 +1,20 @@ +package com.adjust.testapp; + +/** + * Created by nonelse on 22.02.2018 + */ +public class Util { + static Boolean strictParseStringToBoolean(String value) { + if (value == null) { + return null; + } + if (value.equalsIgnoreCase("true")) { + return true; + } + if (value.equalsIgnoreCase("false")) { + return false; + } + + return null; + } +} diff --git a/Adjust/testapp/src/main/res/drawable-v24/ic_launcher_foreground.xml b/Adjust/testapp/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 000000000..c7bd21dbd --- /dev/null +++ b/Adjust/testapp/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/Adjust/testapp/src/main/res/drawable/ic_launcher_background.xml b/Adjust/testapp/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000..d5fccc538 --- /dev/null +++ b/Adjust/testapp/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Adjust/testapp/src/main/res/layout/activity_main.xml b/Adjust/testapp/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..17d7bd290 --- /dev/null +++ b/Adjust/testapp/src/main/res/layout/activity_main.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/Adjust/testapp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/Adjust/testapp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000..eca70cfe5 --- /dev/null +++ b/Adjust/testapp/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Adjust/testapp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/Adjust/testapp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000..eca70cfe5 --- /dev/null +++ b/Adjust/testapp/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Adjust/testapp/src/main/res/mipmap-hdpi/ic_launcher.png b/Adjust/testapp/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..a2f590828 Binary files /dev/null and b/Adjust/testapp/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/Adjust/testapp/src/main/res/mipmap-hdpi/ic_launcher_round.png b/Adjust/testapp/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 000000000..1b5239980 Binary files /dev/null and b/Adjust/testapp/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/Adjust/testapp/src/main/res/mipmap-mdpi/ic_launcher.png b/Adjust/testapp/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..ff10afd6e Binary files /dev/null and b/Adjust/testapp/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/Adjust/testapp/src/main/res/mipmap-mdpi/ic_launcher_round.png b/Adjust/testapp/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 000000000..115a4c768 Binary files /dev/null and b/Adjust/testapp/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/Adjust/testapp/src/main/res/mipmap-xhdpi/ic_launcher.png b/Adjust/testapp/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..dcd3cd808 Binary files /dev/null and b/Adjust/testapp/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/Adjust/testapp/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/Adjust/testapp/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 000000000..459ca609d Binary files /dev/null and b/Adjust/testapp/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/Adjust/testapp/src/main/res/mipmap-xxhdpi/ic_launcher.png b/Adjust/testapp/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..8ca12fe02 Binary files /dev/null and b/Adjust/testapp/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/Adjust/testapp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/Adjust/testapp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..8e19b410a Binary files /dev/null and b/Adjust/testapp/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/Adjust/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/Adjust/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..b824ebdd4 Binary files /dev/null and b/Adjust/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/Adjust/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/Adjust/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 000000000..4c19a13c2 Binary files /dev/null and b/Adjust/testapp/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/Adjust/testapp/src/main/res/values/colors.xml b/Adjust/testapp/src/main/res/values/colors.xml new file mode 100644 index 000000000..3ab3e9cbc --- /dev/null +++ b/Adjust/testapp/src/main/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #3F51B5 + #303F9F + #FF4081 + diff --git a/Adjust/testapp/src/main/res/values/strings.xml b/Adjust/testapp/src/main/res/values/strings.xml new file mode 100644 index 000000000..8e155605e --- /dev/null +++ b/Adjust/testapp/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + testapp + Start Test Session + diff --git a/Adjust/testapp/src/main/res/values/styles.xml b/Adjust/testapp/src/main/res/values/styles.xml new file mode 100644 index 000000000..daa2a5c2f --- /dev/null +++ b/Adjust/testapp/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/Adjust/testlibrary/.gitignore b/Adjust/testlibrary/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/Adjust/testlibrary/.gitignore @@ -0,0 +1 @@ +/build diff --git a/Adjust/testlibrary/build.gradle b/Adjust/testlibrary/build.gradle new file mode 100644 index 000000000..6038184b6 --- /dev/null +++ b/Adjust/testlibrary/build.gradle @@ -0,0 +1,32 @@ +apply plugin: 'com.android.library' + +android { + compileSdkVersion 27 + buildToolsVersion '27.0.3' + + defaultConfig { + minSdkVersion 9 + targetSdkVersion 27 + versionCode 1 + versionName "1.0" + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + + compile 'com.google.code.gson:gson:2.8.0' +} + +task clearJar(type: Delete) { + delete "build/outputs/adjust-testing.jar" +} + +task makeJar(type: Copy) { + from('build/intermediates/bundles/debug/') + into('build/outputs/') + include('classes.jar') + rename('classes.jar', "adjust-testing.jar") +} + +makeJar.dependsOn(clearJar, build) diff --git a/Adjust/testlibrary/proguard-rules.pro b/Adjust/testlibrary/proguard-rules.pro new file mode 100644 index 000000000..f1b424510 --- /dev/null +++ b/Adjust/testlibrary/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/Adjust/testlibrary/src/main/AndroidManifest.xml b/Adjust/testlibrary/src/main/AndroidManifest.xml new file mode 100644 index 000000000..7fa906d59 --- /dev/null +++ b/Adjust/testlibrary/src/main/AndroidManifest.xml @@ -0,0 +1,2 @@ + diff --git a/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/Constants.java b/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/Constants.java new file mode 100644 index 000000000..0d1b894a9 --- /dev/null +++ b/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/Constants.java @@ -0,0 +1,22 @@ +package com.adjust.testlibrary; + +/** + * Created by nonelse on 09.03.17. + */ + +public interface Constants { + int ONE_SECOND = 1000; + int ONE_MINUTE = 60 * ONE_SECOND; + + String ENCODING = "UTF-8"; + + String LOGTAG = "TestLibrary"; + String TEST_SCRIPT_HEADER = "TestScript"; + String BASE_PATH_HEADER = "BasePath"; + String TEST_SESSION_END_HEADER = "TestSessionEnd"; + String TEST_CANCELTEST_HEADER = "CancelTest"; + String TEST_ENDWAIT_HEADER = "EndWait"; + String TEST_LIBRARY_CLASSNAME = "TestLibrary"; + String WAIT_FOR_CONTROL = "control"; + String WAIT_FOR_SLEEP = "sleep"; +} diff --git a/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/ControlChannel.java b/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/ControlChannel.java new file mode 100644 index 000000000..3b8e50956 --- /dev/null +++ b/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/ControlChannel.java @@ -0,0 +1,81 @@ +package com.adjust.testlibrary; + +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import static com.adjust.testlibrary.Constants.TEST_CANCELTEST_HEADER; +import static com.adjust.testlibrary.Constants.TEST_ENDWAIT_HEADER; +import static com.adjust.testlibrary.Utils.debug; +import static com.adjust.testlibrary.UtilsNetworking.sendPostI; + +/** + * Created by nonelse on 21.03.17. + */ + +public class ControlChannel { + private static final String CONTROL_START_PATH = "/control_start"; + private static final String CONTROL_CONTINUE_PATH = "/control_continue"; + + ExecutorService controlChannelExecutor = Executors.newCachedThreadPool(); + TestLibrary testLibrary; + private boolean closed = false; + + public ControlChannel(TestLibrary testLibrary) { + this.testLibrary = testLibrary; + sendControlRequest(CONTROL_START_PATH); + } + + public void teardown() { + if (controlChannelExecutor != null) { + debug("controlChannelExecutor shutdown"); + controlChannelExecutor.shutdown(); + } + + closed = true; + controlChannelExecutor = null; + } + + private void sendControlRequest(final String controlPath) { + controlChannelExecutor.submit(new Runnable() { + @Override + public void run() { + long timeBefore = System.nanoTime(); + debug("time before wait: %d", timeBefore); + + UtilsNetworking.HttpResponse httpResponse = sendPostI( + Utils.appendBasePath(testLibrary.currentBasePath, controlPath)); + + long timeAfter = System.nanoTime(); + long timeElapsedMillis = TimeUnit.NANOSECONDS.toMillis(timeAfter - timeBefore); + debug("time after wait: %d", timeAfter); + debug("time elapsed waiting in milli seconds: %d", timeElapsedMillis); + + readControlHeaders(httpResponse); + } + }); + } + + void readControlHeaders(UtilsNetworking.HttpResponse httpResponse) { + if (closed) { + debug("control channel already closed"); + return; + } + if (httpResponse.headerFields.containsKey(TEST_CANCELTEST_HEADER)) { + debug("Test canceled due to %s", httpResponse.headerFields.get(TEST_CANCELTEST_HEADER).get(0)); + testLibrary.resetTestLibrary(); + testLibrary.readResponse(httpResponse); + } + if (httpResponse.headerFields.containsKey(TEST_ENDWAIT_HEADER)) { + String waitEndReason = httpResponse.headerFields.get(TEST_ENDWAIT_HEADER).get(0); + sendControlRequest(CONTROL_CONTINUE_PATH); + endWait(waitEndReason); + } + } + + void endWait(String waitEndReason) { + debug("End wait from control channel due to %s", waitEndReason); + testLibrary.waitControlQueue.offer(waitEndReason); + debug("Wait ended from control channel due to %s", waitEndReason); + } +} diff --git a/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/ICommandJsonListener.java b/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/ICommandJsonListener.java new file mode 100644 index 000000000..0ba75f5e1 --- /dev/null +++ b/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/ICommandJsonListener.java @@ -0,0 +1,9 @@ +package com.adjust.testlibrary; + +/** + * Created by nonelse on 10.03.17. + */ + +public interface ICommandJsonListener { + void executeCommand(String className, String methodName, String jsonParameters); +} diff --git a/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/ICommandListener.java b/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/ICommandListener.java new file mode 100644 index 000000000..ea8f16590 --- /dev/null +++ b/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/ICommandListener.java @@ -0,0 +1,12 @@ +package com.adjust.testlibrary; + +import java.util.List; +import java.util.Map; + +/** + * Created by nonelse on 09.03.17. + */ + +public interface ICommandListener { + void executeCommand(String className, String methodName, Map> parameters); +} diff --git a/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/ICommandRawJsonListener.java b/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/ICommandRawJsonListener.java new file mode 100644 index 000000000..c74e3e0d5 --- /dev/null +++ b/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/ICommandRawJsonListener.java @@ -0,0 +1,9 @@ +package com.adjust.testlibrary; + +/** + * Created by nonelse on 10.03.17. + */ + +public interface ICommandRawJsonListener { + void executeCommand(String json); +} diff --git a/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/IOnExitListener.java b/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/IOnExitListener.java new file mode 100644 index 000000000..86126a9fd --- /dev/null +++ b/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/IOnExitListener.java @@ -0,0 +1,9 @@ +package com.adjust.testlibrary; + +/** + * Created by nonelse on 09.03.17. + */ + +public interface IOnExitListener { + void onExit(); +} diff --git a/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/TestCommand.java b/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/TestCommand.java new file mode 100644 index 000000000..ca53bed81 --- /dev/null +++ b/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/TestCommand.java @@ -0,0 +1,14 @@ +package com.adjust.testlibrary; + +import java.util.List; +import java.util.Map; + +/** + * Created by nonelse on 09.03.17. + */ + +public class TestCommand { + public String className; + public String functionName; + public Map> params; +} diff --git a/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/TestLibrary.java b/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/TestLibrary.java new file mode 100644 index 000000000..c79cac88b --- /dev/null +++ b/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/TestLibrary.java @@ -0,0 +1,300 @@ +package com.adjust.testlibrary; + +import android.os.SystemClock; + +import com.google.gson.Gson; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.TimeUnit; + +import static com.adjust.testlibrary.Constants.TEST_LIBRARY_CLASSNAME; +import static com.adjust.testlibrary.Constants.WAIT_FOR_CONTROL; +import static com.adjust.testlibrary.Constants.WAIT_FOR_SLEEP; +import static com.adjust.testlibrary.Utils.debug; +import static com.adjust.testlibrary.UtilsNetworking.sendPostI; + + +/** + * Created by nonelse on 09.03.17. + */ + +public class TestLibrary { + static String baseUrl; + ExecutorService executor; + IOnExitListener onExitListener; + ICommandListener commandListener; + ICommandJsonListener commandJsonListener; + ICommandRawJsonListener commandRawJsonListener; + ControlChannel controlChannel; + String currentTestName; + String currentBasePath; + Gson gson = new Gson(); + BlockingQueue waitControlQueue; + Map infoToServer; + + StringBuilder testNames = new StringBuilder(); + boolean exitAfterEnd = true; + + public TestLibrary(String baseUrl, ICommandRawJsonListener commandRawJsonListener) { + this(baseUrl); + this.commandRawJsonListener = commandRawJsonListener; + } + + public TestLibrary(String baseUrl, ICommandJsonListener commandJsonListener) { + this(baseUrl); + this.commandJsonListener = commandJsonListener; + } + + public TestLibrary(String baseUrl, ICommandListener commandListener) { + this(baseUrl); + this.commandListener = commandListener; + } + + private TestLibrary(String baseUrl) { + this.baseUrl = baseUrl; + debug("base url: %s", baseUrl); + } + + // resets test library to initial state + void resetTestLibrary() { + teardown(true); + + executor = Executors.newCachedThreadPool(); + waitControlQueue = new LinkedBlockingQueue(); + } + + // clears test library + private void teardown(boolean shutdownNow) { + if (executor != null) { + if (shutdownNow) { + debug("test library executor shutdownNow"); + executor.shutdownNow(); + } else { + debug("test library executor shutdown"); + executor.shutdown(); + } + } + executor = null; + + clearTest(); + } + + // clear for each test + private void clearTest() { + if (waitControlQueue != null) { + waitControlQueue.clear(); + } + waitControlQueue = null; + if (controlChannel != null) { + controlChannel.teardown(); + } + controlChannel = null; + infoToServer = null; + } + + // reset for each test + private void resetForNextTest() { + clearTest(); + + waitControlQueue = new LinkedBlockingQueue(); + controlChannel = new ControlChannel(this); + } + + public void addTestDirectory(String testDir) { + this.testNames.append(testDir); + + if(!testDir.endsWith("/") || !testDir.endsWith("/;")) { + this.testNames.append("/"); + } + + if(!testDir.endsWith(";")) { + this.testNames.append(";"); + } + } + + public void addTest(String testName) { + this.testNames.append(testName); + + if(!testName.endsWith(";")) { + this.testNames.append(";"); + } + } + + public void doNotExitAfterEnd() { + this.exitAfterEnd = false; + } + + public void startTestSession(final String clientSdk) { + resetTestLibrary(); + + executor.submit(new Runnable() { + @Override + public void run() { + sendTestSessionI(clientSdk); + } + }); + } + + public void setOnExitListener(IOnExitListener onExitListener) { + this.onExitListener = onExitListener; + } + + public void addInfoToSend(String key, String value) { + if (infoToServer == null) { + infoToServer = new HashMap(); + } + + infoToServer.put(key, value); + } + + public void sendInfoToServer(final String basePath) { + executor.submit(new Runnable() { + @Override + public void run() { + sendInfoToServerI(basePath); + } + }); + } + + void readResponse(final UtilsNetworking.HttpResponse httpResponse) { + executor.submit(new Runnable() { + @Override + public void run() { + readResponseI(httpResponse); + } + }); + } + + private void sendTestSessionI(String clientSdk) { + UtilsNetworking.HttpResponse httpResponse = sendPostI("/init_session", clientSdk, testNames.toString()); + readResponseI(httpResponse); + } + + private void sendInfoToServerI(String basePath) { + UtilsNetworking.HttpResponse httpResponse = sendPostI(Utils.appendBasePath(basePath, "/test_info"), null, infoToServer); + infoToServer = null; + readResponseI(httpResponse); + } + + public void readResponseI(UtilsNetworking.HttpResponse httpResponse) { + if (httpResponse == null) { + debug("httpResponse is null"); + return; + } + List testCommands = Arrays.asList(gson.fromJson(httpResponse.response, TestCommand[].class)); + try { + execTestCommandsI(testCommands); + } catch (InterruptedException e) { + debug("InterruptedException thrown %s", e.getMessage()); + } + } + + private void execTestCommandsI(List testCommands) throws InterruptedException { + debug("testCommands: %s", testCommands); + + for (TestCommand testCommand : testCommands) { + if (Thread.interrupted()) { + debug("Thread interrupted"); + return; + } + debug("ClassName: %s", testCommand.className); + debug("FunctionName: %s", testCommand.functionName); + debug("Params:"); + if (testCommand.params != null && testCommand.params.size() > 0) { + for (Map.Entry> entry : testCommand.params.entrySet()) { + debug("\t%s: %s", entry.getKey(), entry.getValue()); + } + } + long timeBefore = System.nanoTime(); + debug("time before %s %s: %d", testCommand.className, testCommand.functionName, timeBefore); + + if (TEST_LIBRARY_CLASSNAME.equals(testCommand.className)) { + executeTestLibraryCommandI(testCommand); + long timeAfter = System.nanoTime(); + long timeElapsedMillis = TimeUnit.NANOSECONDS.toMillis(timeAfter - timeBefore); + debug("time after %s %s: %d", testCommand.className, testCommand.functionName, timeAfter); + debug("time elapsed %s %s in milli seconds: %d", testCommand.className, testCommand.functionName, timeElapsedMillis); + + continue; + } + if (commandListener != null) { + commandListener.executeCommand(testCommand.className, testCommand.functionName, testCommand.params); + } else if (commandJsonListener != null) { + commandJsonListener.executeCommand(testCommand.className, testCommand.functionName, gson.toJson(testCommand.params)); + } else if (commandRawJsonListener != null) { + commandRawJsonListener.executeCommand(gson.toJson(testCommand)); + } + long timeAfter = System.nanoTime(); + long timeElapsedMillis = TimeUnit.NANOSECONDS.toMillis(timeAfter - timeBefore); + debug("time after %s.%s: %d", testCommand.className, testCommand.functionName, timeAfter); + debug("time elapsed %s.%s in milli seconds: %d", testCommand.className, testCommand.functionName, timeElapsedMillis); + } + } + + private void executeTestLibraryCommandI(TestCommand testCommand) throws InterruptedException { + switch (testCommand.functionName) { + case "resetTest": resetTestI(testCommand.params); break; + case "endTestReadNext": endTestReadNext(); break; + case "endTestSession": endTestSessionI(); break; + case "wait": waitI(testCommand.params); break; + case "exit": exit(); break; + } + } + + private void resetTestI(Map> params) { + if (params.containsKey("basePath")) { + currentBasePath = params.get("basePath").get(0); + debug("current base path %s", currentBasePath); + } + + if (params.containsKey("testName")) { + currentTestName = params.get("testName").get(0); + debug("current test name %s", currentTestName); + } + resetForNextTest(); + } + + private void endTestReadNext() { + // send end test request + UtilsNetworking.HttpResponse httpResponse = sendPostI(Utils.appendBasePath(currentBasePath, "/end_test_read_next")); + // and process the next in the response + readResponseI(httpResponse); + } + + private void endTestSessionI() { + teardown(false); + if (exitAfterEnd) { + exit(); + } + } + + private void waitI(Map> params) throws InterruptedException { + if (params.containsKey(WAIT_FOR_CONTROL)) { + String waitExpectedReason = params.get(WAIT_FOR_CONTROL).get(0); + debug("wait for %s", waitExpectedReason); + String endReason = waitControlQueue.take(); + debug("wait ended due to %s", endReason); + } + if (params.containsKey(WAIT_FOR_SLEEP)) { + long millisToSleep = Long.parseLong(params.get(WAIT_FOR_SLEEP).get(0)); + debug("sleep for %s", millisToSleep); + + SystemClock.sleep(millisToSleep); + debug("sleep ended"); + } + } + + private void exit() { + if(onExitListener != null){ + onExitListener.onExit(); + } + System.exit(0); + } +} diff --git a/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/Utils.java b/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/Utils.java new file mode 100644 index 000000000..524b7f697 --- /dev/null +++ b/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/Utils.java @@ -0,0 +1,49 @@ +package com.adjust.testlibrary; + +import android.util.Log; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.util.Arrays; +import java.util.Locale; +import java.util.Map; + +import javax.net.ssl.HttpsURLConnection; + +import static com.adjust.testlibrary.Constants.LOGTAG; +import static com.adjust.testlibrary.UtilsNetworking.connectionOptions; +import static com.adjust.testlibrary.UtilsNetworking.createPOSTHttpsURLConnection; +import static com.adjust.testlibrary.UtilsNetworking.readHttpResponse; + +/** + * Created by nonelse on 11.03.17. + */ + +public class Utils { + + public static void debug(String message, Object... parameters) { + try { + Log.d(LOGTAG, String.format(Locale.US, message, parameters)); + } catch (Exception e) { + Log.e(LOGTAG, String.format(Locale.US, "Error formating log message: %s, with params: %s" + , message, Arrays.toString(parameters))); + } + } + + public static void error(String message, Object... parameters) { + try { + Log.e(LOGTAG, String.format(Locale.US, message, parameters)); + } catch (Exception e) { + Log.e(LOGTAG, String.format(Locale.US, "Error formating log message: %s, with params: %s" + , message, Arrays.toString(parameters))); + } + } + + public static String appendBasePath(String basePath, String path) { + if (basePath == null) { + return path; + } + return basePath + path; + } +} diff --git a/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/UtilsNetworking.java b/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/UtilsNetworking.java new file mode 100644 index 000000000..c12a878b4 --- /dev/null +++ b/Adjust/testlibrary/src/main/java/com/adjust/testlibrary/UtilsNetworking.java @@ -0,0 +1,270 @@ +package com.adjust.testlibrary; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.UnsupportedEncodingException; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.URL; +import java.net.URLEncoder; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +import static com.adjust.testlibrary.Constants.ONE_MINUTE; +import static com.adjust.testlibrary.Utils.debug; +import static com.adjust.testlibrary.Utils.error; + +/** + * Created by uerceg on 03/04/2017. + */ + +public class UtilsNetworking { + static ConnectionOptions connectionOptions; + static TrustManager[] trustAllCerts; + static HostnameVerifier hostnameVerifier; + + public static UtilsNetworking.HttpResponse sendPostI(String path) { + return sendPostI(path, null, null, null); + } + + public static UtilsNetworking.HttpResponse sendPostI(String path, String clientSdk) { + return sendPostI(path, clientSdk, null, null); + } + + public static UtilsNetworking.HttpResponse sendPostI(String path, String clientSdk, String testNames) { + return sendPostI(path, clientSdk, testNames, null); + } + + public static UtilsNetworking.HttpResponse sendPostI(String path, String clientSdk, Map postBody) { + return sendPostI(path, clientSdk, null, postBody); + } + + public static UtilsNetworking.HttpResponse sendPostI(String path, String clientSdk, String testNames, Map postBody) { + String targetURL = TestLibrary.baseUrl + path; + + try { + connectionOptions.clientSdk = clientSdk; + connectionOptions.testNames = testNames; + + HttpsURLConnection connection = createPOSTHttpsURLConnection( + targetURL, postBody, connectionOptions); + UtilsNetworking.HttpResponse httpResponse = readHttpResponse(connection); + debug("Response: %s", httpResponse.response); + + httpResponse.headerFields= connection.getHeaderFields(); + debug("Headers: %s", httpResponse.headerFields); + + return httpResponse; + } catch (IOException e) { + error(e.getMessage()); + } catch (Exception e) { + error(e.getMessage()); + } + return null; + } + + private static String getPostDataString(Map body) throws UnsupportedEncodingException { + StringBuilder result = new StringBuilder(); + + for (Map.Entry entry : body.entrySet()) { + String encodedName = URLEncoder.encode(entry.getKey(), Constants.ENCODING); + String value = entry.getValue(); + String encodedValue = value != null ? URLEncoder.encode(value, Constants.ENCODING) : ""; + + if (result.length() > 0) { + result.append("&"); + } + + result.append(encodedName); + result.append("="); + result.append(encodedValue); + } + + return result.toString(); + } + + static { + trustAllCerts = new TrustManager[]{ + new X509TrustManager() { + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + debug("getAcceptedIssuers"); + + return null; + } + + public void checkClientTrusted( + java.security.cert.X509Certificate[] certs, String authType) { + debug("checkClientTrusted"); + } + + public void checkServerTrusted( + java.security.cert.X509Certificate[] certs, String authType) { + debug("checkServerTrusted"); + } + } + }; + hostnameVerifier = new HostnameVerifier() { + @Override + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; + connectionOptions = new ConnectionOptions(); + } + + public static class HttpResponse { + public String response = null; + public Integer responseCode = null; + public Map> headerFields = null; + } + + public interface IConnectionOptions { + void applyConnectionOptions(HttpsURLConnection connection); + } + + static class ConnectionOptions implements IConnectionOptions { + public String clientSdk; + public String testNames; + + @Override + public void applyConnectionOptions(HttpsURLConnection connection) { + if (clientSdk != null) { + connection.setRequestProperty("Client-SDK", clientSdk); + } + if (testNames != null) { + connection.setRequestProperty("Test-Names", testNames); + } + //Inject local ip address for Jenkins script + connection.setRequestProperty("Local-Ip", getIPAddress(true)); + + connection.setConnectTimeout(ONE_MINUTE); + connection.setReadTimeout(ONE_MINUTE); + try { + SSLContext sc = SSLContext.getInstance("TLS"); + sc.init(null, trustAllCerts, new java.security.SecureRandom()); + connection.setSSLSocketFactory(sc.getSocketFactory()); + + connection.setHostnameVerifier(hostnameVerifier); + debug("applyConnectionOptions"); + } catch (Exception e) { + debug("applyConnectionOptions %s", e.getMessage()); + } + } + } + + static HttpsURLConnection createPOSTHttpsURLConnection(String urlString, + Map postBody, + IConnectionOptions connectionOptions) + throws IOException + { + DataOutputStream wr = null; + HttpsURLConnection connection = null; + + try { + debug("POST request: %s", urlString); + URL url = new URL(urlString); + connection = (HttpsURLConnection) url.openConnection(); + + connectionOptions.applyConnectionOptions(connection); + + connection.setRequestMethod("POST"); + connection.setUseCaches(false); + connection.setDoInput(true); + connection.setDoOutput(true); + + if (postBody != null && postBody.size() > 0) { + wr = new DataOutputStream(connection.getOutputStream()); + wr.writeBytes(getPostDataString(postBody)); + } + + return connection; + } catch (Exception e) { + throw e; + } finally { + try { + if (wr != null) { + wr.flush(); + wr.close(); + } + } catch (Exception e) { + } + } + } + + static HttpResponse readHttpResponse(HttpsURLConnection connection) throws Exception { + StringBuffer sb = new StringBuffer(); + HttpResponse httpResponse = new HttpResponse(); + + try { + connection.connect(); + + httpResponse.responseCode = connection.getResponseCode(); + InputStream inputStream; + + if (httpResponse.responseCode >= 400) { + inputStream = connection.getErrorStream(); + } else { + inputStream = connection.getInputStream(); + } + + InputStreamReader inputStreamReader = new InputStreamReader(inputStream); + BufferedReader bufferedReader = new BufferedReader(inputStreamReader); + + String line; + + while ((line = bufferedReader.readLine()) != null) { + sb.append(line); + } + } catch (Exception e) { + error("Failed to read response. (%s)", e.getMessage()); + throw e; + } finally { + if (connection != null) { + connection.disconnect(); + } + } + + httpResponse.response = sb.toString(); + return httpResponse; + } + + static String getIPAddress(boolean useIPv4) { + try { + List interfaces = Collections.list(NetworkInterface.getNetworkInterfaces()); + for (NetworkInterface intf : interfaces) { + List addrs = Collections.list(intf.getInetAddresses()); + for (InetAddress addr : addrs) { + if (!addr.isLoopbackAddress()) { + String sAddr = addr.getHostAddress(); + boolean isIPv4 = sAddr.indexOf(':') < 0; + + if (useIPv4) { + if (isIPv4) + return sAddr; + } else { + if (!isIPv4) { + int delim = sAddr.indexOf('%'); // drop ip6 zone suffix + return delim < 0 ? sAddr.toUpperCase() : sAddr.substring(0, delim).toUpperCase(); + } + } + } + } + } + } catch (Exception ex) { + error("Failed to read ip address (%s)", ex.getMessage()); + } + + return ""; + } +} diff --git a/Adjust/testlibrary/src/main/res/values/strings.xml b/Adjust/testlibrary/src/main/res/values/strings.xml new file mode 100644 index 000000000..a4019487d --- /dev/null +++ b/Adjust/testlibrary/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + testlibrary + diff --git a/CHANGELOG.md b/CHANGELOG.md index 32a2f7eaa..f7d6f674e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +### Version 4.12.2 (28th February 2018) +#### Changed +- Capturing information about silently ignored runtime exceptions by scheduled executor. +- Send referrer information upon enabling SDK if it was launched as disabled. + +#### Fixed +- Fixed handling of malformed referrer string values. + +--- + ### Version 4.12.1 (31st January 2018) #### Fixed - Formatting all strings with US locale. diff --git a/README.md b/README.md index 176231718..768a01c84 100644 --- a/README.md +++ b/README.md @@ -72,14 +72,14 @@ These are the minimal steps required to integrate the Adjust SDK into your Andro If you are using Maven, add the following to your `build.gradle` file: ``` -compile 'com.adjust.sdk:adjust-android:4.12.1' +compile 'com.adjust.sdk:adjust-android:4.12.2' compile 'com.android.installreferrer:installreferrer:1.0' ``` **Note**: If you are using `Gradle 3.0.0 or above`, make sure to use the `implementation` keyword instead of `compile` as follows: ``` -implementation 'com.adjust.sdk:adjust-android:4.12.1' +implementation 'com.adjust.sdk:adjust-android:4.12.2' implementation 'com.android.installreferrer:installreferrer:1.0' ``` @@ -184,7 +184,7 @@ Also, make sure that you have paid attention to the [Proguard settings](#sdk-pro -keep public class com.android.installreferrer.** { *; } ``` -This feature is supported if you are using **Adjust SDK v4.12.1 or above**. +This feature is supported if you are using **Adjust SDK v4.12.0 or above**. #### Google Play Store intent @@ -1003,7 +1003,7 @@ You can also remove the `-n com.your.appid/com.adjust.sdk.AdjustReferrerReceiver If you set the log level to `verbose`, you should be able to see the log from reading the referrer: ``` -V/Adjust: Reading query string (adjust_reftag=abc1234&tracking_id=123456789&utm_source=network&utm_medium=banner&utm_campaign=campaign) from reftag +V/Adjust: Referrer to parse (adjust_reftag=abc1234&tracking_id=123456789&utm_source=network&utm_medium=banner&utm_campaign=campaign) from reftag ``` And a click package added to the SDK's package handler: diff --git a/VERSION b/VERSION index 53cf85e17..f1cd7de1d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -4.12.1 +4.12.2 diff --git a/doc/english/criteo_plugin.md b/doc/english/criteo_plugin.md index 28154faab..c2dcf6698 100644 --- a/doc/english/criteo_plugin.md +++ b/doc/english/criteo_plugin.md @@ -3,7 +3,7 @@ Add the dependency of the adjust sdk with the Criteo plugin: ``` -compile 'com.adjust.sdk:adjust-android-criteo:4.12.1' +compile 'com.adjust.sdk:adjust-android-criteo:4.12.2' ``` Or integrate adjust with Criteo events by following these steps: diff --git a/doc/english/migrate.md b/doc/english/migrate.md index 758514918..938086d7c 100644 --- a/doc/english/migrate.md +++ b/doc/english/migrate.md @@ -1,4 +1,4 @@ -## Migrate your adjust SDK for Android to 4.12.1 from 3.6.2 +## Migrate your adjust SDK for Android to 4.12.2 from 3.6.2 ### The Application class diff --git a/doc/english/sociomantic_plugin.md b/doc/english/sociomantic_plugin.md index 2c2c16fa1..8a3b54183 100644 --- a/doc/english/sociomantic_plugin.md +++ b/doc/english/sociomantic_plugin.md @@ -3,7 +3,7 @@ Add the dependency of the adjust sdk with the Sociomantic plugin: ``` -compile 'com.adjust.sdk:adjust-android-sociomantic:4.12.1' +compile 'com.adjust.sdk:adjust-android-sociomantic:4.12.2' ``` Or integrate adjust with Sociomantic events by following these steps: diff --git a/doc/english/trademob_plugin.md b/doc/english/trademob_plugin.md index 3a9c0900a..d86bed4ac 100644 --- a/doc/english/trademob_plugin.md +++ b/doc/english/trademob_plugin.md @@ -3,7 +3,7 @@ Add the dependency of the adjust sdk with the Trademob plugin: ``` -compile 'com.adjust.sdk:adjust-android-trademob:4.12.1' +compile 'com.adjust.sdk:adjust-android-trademob:4.12.2' ``` Or integrate adjust with Trademob events by following these steps: