diff --git a/android-core/build.gradle b/android-core/build.gradle index 8c9cd304c..134dec61b 100644 --- a/android-core/build.gradle +++ b/android-core/build.gradle @@ -90,6 +90,7 @@ android { lintConfig file('lint-baseline.xml') } sourceSets { + main.kotlin.srcDirs += 'src/main/kotlin' test.java.srcDirs += 'src/test/kotlin' androidTest.java.srcDirs += 'src/androidTest/kotlin' } @@ -165,16 +166,6 @@ configurations { } } -afterEvaluate { - android.buildTypes.all { theBuildType -> - android.sourceSets.all { sourceSet -> - if (!sourceSet.name.startsWith("test") && !sourceSet.name.startsWith("androidTest")) { - sourceSet.kotlin.setSrcDirs([]) - } - } - } -} - boolean useOrchestrator() { return project.hasProperty('orchestrator') ? project.property('orchestrator') : false } \ No newline at end of file diff --git a/android-core/proguard.pro b/android-core/proguard.pro index 0450e0ce6..208c2a6e5 100644 --- a/android-core/proguard.pro +++ b/android-core/proguard.pro @@ -108,6 +108,8 @@ -keep class com.mparticle.AttributionResult { *; } -keep class com.mparticle.MParticleTask { *; } -keep class com.mparticle.UserAttributeListener { *; } +-keep class com.mparticle.TypedUserAttributeListener { *; } +-keep class com.mparticle.UserAttributeListenerType { *; } -keep class com.mparticle.BaseEvent { *; } -keep class com.mparticle.BaseEvent$Type { *; } -keep class com.mparticle.BaseEvent$MessageType { *; } diff --git a/android-core/src/androidTest/java/com/mparticle/identity/MParticleUserDelegateTest.java b/android-core/src/androidTest/java/com/mparticle/identity/MParticleUserDelegateITest.java similarity index 99% rename from android-core/src/androidTest/java/com/mparticle/identity/MParticleUserDelegateTest.java rename to android-core/src/androidTest/java/com/mparticle/identity/MParticleUserDelegateITest.java index 130f655ad..c1eedc525 100644 --- a/android-core/src/androidTest/java/com/mparticle/identity/MParticleUserDelegateTest.java +++ b/android-core/src/androidTest/java/com/mparticle/identity/MParticleUserDelegateITest.java @@ -30,7 +30,7 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; -public class MParticleUserDelegateTest extends BaseCleanStartedEachTest { +public class MParticleUserDelegateITest extends BaseCleanStartedEachTest { MParticleUserDelegate mUserDelegate; @Before diff --git a/android-core/src/androidTest/java/com/mparticle/internal/database/services/MParticleDBManagerTest.java b/android-core/src/androidTest/java/com/mparticle/internal/database/services/MParticleDBManagerTest.java index 1c11a7a04..5eea73313 100644 --- a/android-core/src/androidTest/java/com/mparticle/internal/database/services/MParticleDBManagerTest.java +++ b/android-core/src/androidTest/java/com/mparticle/internal/database/services/MParticleDBManagerTest.java @@ -2,9 +2,13 @@ import android.os.Handler; import android.os.Looper; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.mparticle.TypedUserAttributeListener; import com.mparticle.UserAttributeListener; +import com.mparticle.identity.UserAttributeListenerWrapper; import com.mparticle.testutils.AndroidUtils.Mutable; import com.mparticle.testutils.BaseCleanInstallEachTest; import com.mparticle.testutils.MPLatch; @@ -109,7 +113,7 @@ public void testGetUserAttributesAsync() throws InterruptedException { final Mutable dbAccessThread = new Mutable(null); final MParticleDBManager manager = new MParticleDBManager(){ @Override - public TreeMap getUserAttributeSingles(long mpId) { + public Map getUserAttributeSingles(long mpId) { dbAccessThread.value = Thread.currentThread(); return null; } @@ -126,13 +130,14 @@ public TreeMap> getUserAttributeLists(long mpId) { //when not on the main thread, it should callback on the current thread, and access the DB on the same thread assertNotEquals("main", Thread.currentThread().getName()); - manager.getUserAttributes(new UserAttributeListener() { + TypedUserAttributeListener listener = new TypedUserAttributeListener() { @Override - public void onUserAttributesReceived(@Nullable Map userAttributes, @Nullable Map> userAttributeLists, @Nullable Long mpid) { + public void onUserAttributesReceived(@NonNull Map userAttributes, @NonNull Map> userAttributeLists, long mpid) { callbackThread.value = Thread.currentThread(); latch.value.countDown(); } - }, 1); + }; + manager.getUserAttributes(new UserAttributeListenerWrapper(listener), 1); assertNotNull(callbackThread.value); assertEquals(Thread.currentThread().getName(), callbackThread.value.getName()); @@ -143,16 +148,17 @@ public void onUserAttributesReceived(@Nullable Map userAttribute latch.value = new MPLatch(1); //when run from the main thread, it should be called back on the main thread, but NOT access the DB on the same thread + TypedUserAttributeListener listener1 = new TypedUserAttributeListener() { + @Override + public void onUserAttributesReceived(@NonNull Map userAttributes, @NonNull Map> userAttributeLists, long mpid) { + callbackThread.value = Thread.currentThread(); + latch.value.countDown(); + } + }; new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { - manager.getUserAttributes(new UserAttributeListener() { - @Override - public void onUserAttributesReceived(@Nullable Map userAttributes, @Nullable Map> userAttributeLists, @Nullable Long mpid) { - callbackThread.value = Thread.currentThread(); - latch.value.countDown(); - } - }, 1); + manager.getUserAttributes(new UserAttributeListenerWrapper(listener), 1); } }); latch.value.await(); diff --git a/android-core/src/androidTest/kotlin/com.mparticle/MPUserTest.kt b/android-core/src/androidTest/kotlin/com.mparticle/MPUserTest.kt new file mode 100644 index 000000000..c68d3ad9c --- /dev/null +++ b/android-core/src/androidTest/kotlin/com.mparticle/MPUserTest.kt @@ -0,0 +1,187 @@ +package com.mparticle + +import com.mparticle.internal.AccessUtils +import com.mparticle.testutils.BaseCleanStartedEachTest +import org.junit.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class MPUserTest : BaseCleanStartedEachTest() { + + @Test + fun testGetAttributeSyncWithAndroidHack() { + MParticle.getInstance()!!.Identity().currentUser!!.apply { + assertTrue { userAttributes.isEmpty() } + setUserAttribute("foo", "bar") + + android_test_hack() + + assertEquals(1, userAttributes.size) + assertEquals("bar", userAttributes["foo"]) + } + } + + @Test + fun testGetAttributeAsync() { + MParticle.getInstance()!!.Identity().currentUser!!.apply { + assertTrue { getUserAttributes().isEmpty() } + setUserAttribute("foo", "bar") + setUserAttribute("fooInt", 123) + setUserAttribute("fooLong", 12345L) + setUserAttribute("fooDouble", 10.15) + setUserAttribute("fooNegInt", -10L) + setUserAttribute("fooNegLong", -1010L) + android_test_hack() + + getUserAttributes(object : UserAttributeListener { + override fun onUserAttributesReceived( + userAttributes: Map?, + userAttributeLists: Map>?, + mpid: Long? + ) { + assertNotNull(userAttributes) + assertEquals(6, userAttributes.size) + assertEquals("bar", userAttributes["foo"]) + assertEquals("123", userAttributes["fooInt"]) + assertEquals("12345", userAttributes["fooLong"]) + assertEquals("10.15", userAttributes["fooDouble"]) + assertEquals("-10", userAttributes["fooNegInt"]) + assertEquals("-1010", userAttributes["fooNegLong"]) + } + }) + + getUserAttributes(object : TypedUserAttributeListener { + override fun onUserAttributesReceived( + userAttributes: Map, + userAttributeLists: Map?>, + mpid: Long + ) { + assertEquals(6, userAttributes.size) + assertEquals("bar", userAttributes["foo"]) + assertEquals(123L, userAttributes["fooInt"]) + assertEquals(12345L, userAttributes["fooLong"]) + assertEquals(10.15, userAttributes["fooDouble"]) + assertEquals(-10L, userAttributes["fooNegInt"]) + assertEquals(-1010L, userAttributes["fooNegLong"]) + } + }) + } + } + + @Test + fun testIncrementIntegerAttribute() { + MParticle.getInstance()!!.Identity().currentUser!!.apply { + assertTrue { getUserAttributes().isEmpty() } + setUserAttribute("foo", 1) + + android_test_hack() + assertEquals(1, userAttributes.size) + incrementUserAttribute("foo", 3) + + android_test_hack() + assertEquals(4L, userAttributes["foo"]) + + // test negative increment + incrementUserAttribute("foo", -2) + android_test_hack() + assertEquals(2L, userAttributes["foo"]) + + // test remove incremented attribute + removeUserAttribute("foo") + android_test_hack() + assertEquals(0, userAttributes.size) + } + } + + @Test + fun testIncrementDoubleAttribute() { + MParticle.getInstance()!!.Identity().currentUser!!.apply { + assertTrue { getUserAttributes().isEmpty() } + android_test_hack() + + setUserAttribute("foo", 1.5) + + android_test_hack() + assertEquals(1, userAttributes.size) + incrementUserAttribute("foo", 3.2) + + android_test_hack() + assertEquals(4.7, userAttributes["foo"]) + + android_test_hack() + + // test negative increment + incrementUserAttribute("foo", -2.1) + android_test_hack() + assertEquals(2.6, userAttributes["foo"]) + + // test remove incremented attribute + removeUserAttribute("foo") + android_test_hack() + assertEquals(0, userAttributes.size) + } + } + + @Test + fun testIncrementLongAttribute() { + MParticle.getInstance()!!.Identity().currentUser!!.apply { + assertTrue { getUserAttributes().isEmpty() } + setUserAttribute("foo", 10L) + + android_test_hack() + assertEquals(1, userAttributes.size) + assertEquals(10L, userAttributes["foo"]) + incrementUserAttribute("foo", 37L) + + android_test_hack() + assertEquals(47L, userAttributes["foo"]) + + // test negative increment + incrementUserAttribute("foo", -21L) + android_test_hack() + assertEquals(26L, userAttributes["foo"]) + + // test remove incremented attribute + removeUserAttribute("foo") + android_test_hack() + assertEquals(0, userAttributes.size) + } + } + + @Test + fun testRemoveUserAttribute() { + MParticle.getInstance()!!.Identity().currentUser!!.apply { + assertTrue { userAttributes.isEmpty() } + setUserAttribute("foo", "bar") + removeUserAttribute("foo") + + android_test_hack() + assertEquals(0, userAttributes.size) + + setUserAttribute("foo", "bar") + setUserAttribute("fuzz", "baz") + + android_test_hack() + assertEquals(2, userAttributes.size) + assertEquals("bar", userAttributes["foo"]) + assertEquals("baz", userAttributes["fuzz"]) + + // remove just 1 + removeUserAttribute("fuzz") + android_test_hack() + assertEquals(1, userAttributes?.size) + assertEquals("bar", userAttributes["foo"]) + + // remove last + removeUserAttribute("foo") + android_test_hack() + assertEquals(0, userAttributes?.size) + } + } + + private fun android_test_hack() { + // force sync attribute writes to complete for Android + AccessUtils.awaitMessageHandler() + } +} diff --git a/android-core/src/main/java/com/mparticle/UserAttributeListener.java b/android-core/src/main/java/com/mparticle/UserAttributeListener.java index 49c2fd0c6..2b79fecca 100644 --- a/android-core/src/main/java/com/mparticle/UserAttributeListener.java +++ b/android-core/src/main/java/com/mparticle/UserAttributeListener.java @@ -5,6 +5,10 @@ import java.util.List; import java.util.Map; -public interface UserAttributeListener { +/** + * @deprecated Use TypedUserAttributeListener instead + */ +@Deprecated +public interface UserAttributeListener extends UserAttributeListenerType { void onUserAttributesReceived(@Nullable Map userAttributes, @Nullable Map> userAttributeLists, @Nullable Long mpid); -} \ No newline at end of file +} diff --git a/android-core/src/main/java/com/mparticle/identity/MParticleUser.java b/android-core/src/main/java/com/mparticle/identity/MParticleUser.java index 558adbd61..cc243bb49 100644 --- a/android-core/src/main/java/com/mparticle/identity/MParticleUser.java +++ b/android-core/src/main/java/com/mparticle/identity/MParticleUser.java @@ -5,7 +5,7 @@ import androidx.annotation.WorkerThread; import com.mparticle.MParticle; -import com.mparticle.UserAttributeListener; +import com.mparticle.UserAttributeListenerType; import com.mparticle.consent.ConsentState; import java.util.Map; @@ -37,7 +37,7 @@ public interface MParticleUser { * @return */ @Nullable - Map getUserAttributes(@Nullable final UserAttributeListener listener); + Map getUserAttributes(@Nullable final UserAttributeListenerType listener); /** * assign attributes to the User in bulk @@ -85,7 +85,7 @@ public interface MParticleUser { * * @return whether the attributes were successfully set */ - boolean incrementUserAttribute(@NonNull String key, int value); + boolean incrementUserAttribute(@NonNull String key, Number value); /** * remove an attribute for the user diff --git a/android-core/src/main/java/com/mparticle/identity/MParticleUserDelegate.java b/android-core/src/main/java/com/mparticle/identity/MParticleUserDelegate.java index d90d2a37c..16e1359c8 100644 --- a/android-core/src/main/java/com/mparticle/identity/MParticleUserDelegate.java +++ b/android-core/src/main/java/com/mparticle/identity/MParticleUserDelegate.java @@ -4,7 +4,7 @@ import android.os.Build; import com.mparticle.MParticle; -import com.mparticle.UserAttributeListener; +import com.mparticle.UserAttributeListenerType; import com.mparticle.consent.ConsentState; import com.mparticle.internal.AppStateManager; import com.mparticle.internal.ConfigManager; @@ -21,7 +21,10 @@ import org.json.JSONException; import org.json.JSONObject; +import java.text.NumberFormat; +import java.text.ParseException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -42,8 +45,8 @@ public Map getUserAttributes(long mpId) { return mMessageManager.getUserAttributes(null, mpId); } - public Map getUserAttributes(final UserAttributeListener listener, long mpId) { - return mMessageManager.getUserAttributes(listener, mpId); + public Map getUserAttributes(final UserAttributeListenerType listener, long mpId) { + return mMessageManager.getUserAttributes(new UserAttributeListenerWrapper(listener), mpId); } public Map getUserIdentities(long mpId){ @@ -202,7 +205,7 @@ public boolean setUserAttributeList(String key, Object value, long userMpId) { return setUserAttribute(key, value, userMpId); } - public boolean incrementUserAttribute(String key, int value, long userMpId) { + public boolean incrementUserAttribute(String key, Number value, long userMpId) { if (key == null) { Logger.warning("incrementUserAttribute called with a null key. Ignoring..."); return false; diff --git a/android-core/src/main/java/com/mparticle/identity/MParticleUserImpl.java b/android-core/src/main/java/com/mparticle/identity/MParticleUserImpl.java index 792752426..48443495b 100644 --- a/android-core/src/main/java/com/mparticle/identity/MParticleUserImpl.java +++ b/android-core/src/main/java/com/mparticle/identity/MParticleUserImpl.java @@ -5,7 +5,7 @@ import androidx.annotation.NonNull; import com.mparticle.MParticle; -import com.mparticle.UserAttributeListener; +import com.mparticle.UserAttributeListenerType; import com.mparticle.consent.ConsentState; import com.mparticle.internal.listeners.ApiClass; import com.mparticle.segmentation.SegmentListener; @@ -49,7 +49,7 @@ public Map getUserAttributes() { * * @return */ - public Map getUserAttributes(final UserAttributeListener listener) { + public Map getUserAttributes(final UserAttributeListenerType listener) { return mUserDelegate.getUserAttributes(listener, getId()); } @@ -96,7 +96,7 @@ public boolean setUserAttributeList(String key, Object value) { } @Override - public boolean incrementUserAttribute(String key, int value) { + public boolean incrementUserAttribute(String key, Number value) { return mUserDelegate.incrementUserAttribute(key, value, getId()); } diff --git a/android-core/src/main/java/com/mparticle/internal/KitFrameworkWrapper.java b/android-core/src/main/java/com/mparticle/internal/KitFrameworkWrapper.java index d774d1b4a..6261e9d86 100644 --- a/android-core/src/main/java/com/mparticle/internal/KitFrameworkWrapper.java +++ b/android-core/src/main/java/com/mparticle/internal/KitFrameworkWrapper.java @@ -243,7 +243,7 @@ boolean queueAttributeTag(String key, long mpid) { return queueAttribute(new AttributeChange(key, mpid, AttributeChange.TAG)); } - boolean queueAttributeIncrement(String key, int incrementedBy, String newValue, long mpid) { + boolean queueAttributeIncrement(String key, Number incrementedBy, String newValue, long mpid) { return queueAttribute(new AttributeChange(key, incrementedBy, newValue, mpid)); } @@ -264,7 +264,7 @@ static class AttributeChange { final Object value; final long mpid; final int type; - int incrementedBy; + Number incrementedBy; static final int REMOVE_ATTRIBUTE = 1; static final int SET_ATTRIBUTE = 2; @@ -292,7 +292,7 @@ static class AttributeChange { this.type = type; } - AttributeChange(String key, int incrementedBy, String newValue, long mpid) { + AttributeChange(String key, Number incrementedBy, String newValue, long mpid) { this.key = key; this.value = newValue; this.incrementedBy = incrementedBy; @@ -397,7 +397,7 @@ public void setUserTag(String tag, long mpid) { } @Override - public void incrementUserAttribute(String key, int incrementValue, String newValue, long mpid) { + public void incrementUserAttribute(String key, Number incrementValue, String newValue, long mpid) { if (!queueAttributeIncrement(key, incrementValue, newValue, mpid) && mKitManager != null) { mKitManager.incrementUserAttribute(key, incrementValue, newValue, mpid); } diff --git a/android-core/src/main/java/com/mparticle/internal/KitManager.java b/android-core/src/main/java/com/mparticle/internal/KitManager.java index 2ee3ca686..2f7bfda51 100644 --- a/android-core/src/main/java/com/mparticle/internal/KitManager.java +++ b/android-core/src/main/java/com/mparticle/internal/KitManager.java @@ -19,7 +19,6 @@ import com.mparticle.identity.MParticleUser; import org.json.JSONArray; -import org.json.JSONObject; import java.lang.ref.WeakReference; import java.util.List; @@ -56,7 +55,7 @@ public interface KitManager { void setUserTag(String tag, long mpid); - void incrementUserAttribute(String key, int incrementValue, String newValue, long mpid); + void incrementUserAttribute(String key, Number incrementValue, String newValue, long mpid); void onConsentStateUpdated(ConsentState oldState, ConsentState newState, long mpid); diff --git a/android-core/src/main/java/com/mparticle/internal/MPUtility.java b/android-core/src/main/java/com/mparticle/internal/MPUtility.java index 51afe4187..66abc315d 100644 --- a/android-core/src/main/java/com/mparticle/internal/MPUtility.java +++ b/android-core/src/main/java/com/mparticle/internal/MPUtility.java @@ -24,6 +24,7 @@ import androidx.annotation.WorkerThread; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.view.Display; import android.view.WindowManager; @@ -49,6 +50,8 @@ import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.text.NumberFormat; +import java.text.ParseException; import java.util.ArrayList; import java.util.Collection; import java.util.Date; @@ -821,6 +824,33 @@ public static String getProp(String key) { return null; } + public static Number addNumbers(Number number1, Number number2) { + if(number1 instanceof Double || number2 instanceof Double) { + return number1.doubleValue() + number2.doubleValue(); + } else if(number1 instanceof Float || number2 instanceof Float) { + return number1.floatValue() + number2.floatValue(); + } else if(number1 instanceof Long || number2 instanceof Long) { + return number1.longValue() + number2.longValue(); + } else { + return number1.intValue() + number2.intValue(); + } + } + + public static Object toNumberOrString(String stringValue) { + if (stringValue == null) { + return stringValue; + } + for(Character c: stringValue.toCharArray()) { + if (!Character.isDigit(c) && c != '.' && c != '-') { + return stringValue; + } + } + try { + return NumberFormat.getInstance().parse(stringValue); + } catch (ParseException e) { } + return stringValue; + } + private interface SyncRunnable { T run(); } diff --git a/android-core/src/main/java/com/mparticle/internal/MessageHandler.java b/android-core/src/main/java/com/mparticle/internal/MessageHandler.java index 326f33895..6f5b02639 100644 --- a/android-core/src/main/java/com/mparticle/internal/MessageHandler.java +++ b/android-core/src/main/java/com/mparticle/internal/MessageHandler.java @@ -7,6 +7,7 @@ import com.mparticle.MParticle; import com.mparticle.internal.Constants.MessageKey; import com.mparticle.internal.Constants.MessageType; +import com.mparticle.internal.MessageManager.IncrementUserAttributeMessage; import com.mparticle.internal.database.services.MParticleDBManager; import com.mparticle.internal.database.tables.SessionTable; import com.mparticle.internal.messages.BaseMPMessage; @@ -15,6 +16,8 @@ import org.json.JSONException; import org.json.JSONObject; +import java.text.NumberFormat; +import java.text.ParseException; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -228,8 +231,8 @@ public void handleMessageImpl(Message msg) { break; case INCREMENT_USER_ATTRIBUTE: try { - Map.Entry obj = (Map.Entry)msg.obj; - incrementUserAttribute(obj.getKey(), msg.arg1, obj.getValue()); + IncrementUserAttributeMessage obj = (IncrementUserAttributeMessage)msg.obj; + incrementUserAttribute(obj); } catch (Exception e) { Logger.error(e, "Error while incrementing user attribute: ", e.toString()); } @@ -262,39 +265,35 @@ void setUserAttributes(MParticleDBManager.UserAttributeResponse response) { } } - private void incrementUserAttribute(String key, int incrementValue, long mpId) { - TreeMap userAttributes = mMParticleDBManager.getUserAttributeSingles(mpId); + private void incrementUserAttribute(IncrementUserAttributeMessage message) { + Map userAttributes = mMParticleDBManager.getUserAttributeSingles(message.mpid); - if (!userAttributes.containsKey(key)) { - TreeMap> userAttributeList = mMParticleDBManager.getUserAttributeLists(mpId); - if (userAttributeList.containsKey(key)) { + if (!userAttributes.containsKey(message.key)) { + TreeMap> userAttributeList = mMParticleDBManager.getUserAttributeLists(message.mpid); + if (userAttributeList.containsKey(message.key)) { Logger.error("Error while attempting to increment user attribute - existing attribute is a list, which can't be incremented."); return; } } String newValue = null; - String currentValue = userAttributes.get(key); + Object currentValue = userAttributes.get(message.key); if (currentValue == null) { - newValue = Integer.toString(incrementValue); - } else { - try { - newValue = Integer.toString(Integer.parseInt(currentValue) + incrementValue); - }catch (NumberFormatException nfe) { - Logger.error("Error while attempting to increment user attribute - existing attribute is not a number."); - return; - } + newValue = message.incrementBy.toString(); + } else if (currentValue instanceof Number) { + newValue = MPUtility.addNumbers((Number) currentValue, message.incrementBy).toString(); + Logger.info("incrementing attribute: \"" + message.key + "\" from: " + currentValue + " by: " + message.incrementBy + " to: " + newValue); } MParticleDBManager.UserAttributeResponse wrapper = new MParticleDBManager.UserAttributeResponse(); - wrapper.attributeSingles = new HashMap(1); - wrapper.attributeSingles.put(key, newValue); - wrapper.mpId = mpId; + wrapper.attributeSingles = new HashMap<>(1); + wrapper.attributeSingles.put(message.key, newValue); + wrapper.mpId = message.mpid; List attributionChanges = mMParticleDBManager.setUserAttribute(wrapper); for (MParticleDBManager.AttributionChange attributeChange: attributionChanges) { logUserAttributeChanged(attributeChange); } MParticle instance = MParticle.getInstance(); if (instance != null && instance.Internal().getKitManager() != null) { - instance.Internal().getKitManager().incrementUserAttribute(key, incrementValue, newValue, mpId); + instance.Internal().getKitManager().incrementUserAttribute(message.key, message.incrementBy, newValue, message.mpid); } } diff --git a/android-core/src/main/java/com/mparticle/internal/MessageManager.java b/android-core/src/main/java/com/mparticle/internal/MessageManager.java index fc646ef92..a22131d9b 100644 --- a/android-core/src/main/java/com/mparticle/internal/MessageManager.java +++ b/android-core/src/main/java/com/mparticle/internal/MessageManager.java @@ -24,8 +24,10 @@ import com.mparticle.MParticle; import com.mparticle.MParticleOptions; import com.mparticle.UserAttributeListener; +import com.mparticle.UserAttributeListenerType; import com.mparticle.commerce.CommerceEvent; import com.mparticle.identity.AliasRequest; +import com.mparticle.identity.UserAttributeListenerWrapper; import com.mparticle.internal.Constants.MessageKey; import com.mparticle.internal.Constants.MessageType; import com.mparticle.internal.database.services.MParticleDBManager; @@ -856,7 +858,7 @@ public void logAll(List messageList) { } } - public Map getUserAttributes(final UserAttributeListener listener, long mpId) { + public Map getUserAttributes(final UserAttributeListenerWrapper listener, long mpId) { return mMParticleDBManager.getUserAttributes(listener, mpId); } @@ -895,11 +897,11 @@ public void setUserAttribute(String key, Object value, long mpId, boolean synchr container.time = System.currentTimeMillis(); container.mpId = mpId; if (value instanceof List) { - container.attributeLists = new HashMap>(); + container.attributeLists = new HashMap<>(); container.attributeLists.put(key, (List) value); }else { - container.attributeSingles = new HashMap(); - container.attributeSingles.put(key, (String) value); + container.attributeSingles = new HashMap<>(); + container.attributeSingles.put(key, value); } if (synchronously) { mMessageHandler.setUserAttributes(container); @@ -909,10 +911,9 @@ public void setUserAttribute(String key, Object value, long mpId, boolean synchr } } - public void incrementUserAttribute(String key, int value, long mpId) { - Map.Entry entry = new HashMap.SimpleEntry(key, mpId); - Message message = mMessageHandler.obtainMessage(MessageHandler.INCREMENT_USER_ATTRIBUTE, entry); - message.arg1 = value; + public void incrementUserAttribute(String key, Number value, long mpId) { + IncrementUserAttributeMessage incrementUserAttributeMessage = new IncrementUserAttributeMessage(key, mpId, value); + Message message = mMessageHandler.obtainMessage(MessageHandler.INCREMENT_USER_ATTRIBUTE, incrementUserAttributeMessage); mMessageHandler.sendMessage(message); } @@ -1062,4 +1063,16 @@ public ReportingMpidMessage(List reportingMessag boolean hasDelayedStartOccurred() { return delayedStartOccurred; } + + static class IncrementUserAttributeMessage { + String key; + Long mpid; + Number incrementBy; + + IncrementUserAttributeMessage(String key, Long mpid, Number incrementBy) { + this.key = key; + this.mpid = mpid; + this.incrementBy = incrementBy; + } + } } diff --git a/android-core/src/main/java/com/mparticle/internal/database/services/MParticleDBManager.java b/android-core/src/main/java/com/mparticle/internal/database/services/MParticleDBManager.java index eebe22f7f..530f65d72 100644 --- a/android-core/src/main/java/com/mparticle/internal/database/services/MParticleDBManager.java +++ b/android-core/src/main/java/com/mparticle/internal/database/services/MParticleDBManager.java @@ -11,7 +11,10 @@ import com.mparticle.MParticle; import com.mparticle.MParticleOptions; +import com.mparticle.TypedUserAttributeListener; import com.mparticle.UserAttributeListener; +import com.mparticle.UserAttributeListenerType; +import com.mparticle.identity.UserAttributeListenerWrapper; import com.mparticle.internal.BatchId; import com.mparticle.internal.ConfigManager; import com.mparticle.internal.Constants; @@ -33,6 +36,8 @@ import org.json.JSONException; import org.json.JSONObject; +import java.text.NumberFormat; +import java.text.ParseException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -549,9 +554,16 @@ public void insertAliasRequest(String apiKey, JSONObject request) { * */ - public TreeMap getUserAttributeSingles(long mpId) { + public Map getUserAttributeSingles(long mpId) { if (getDatabase() != null) { - return UserAttributesService.getUserAttributesSingles(getDatabase(), mpId); + Map stringifiedAttributes = UserAttributesService.getUserAttributesSingles(getDatabase(), mpId); + Map typedAttributes = new HashMap<>(); + for (Map.Entry stringifiedAttribute: stringifiedAttributes.entrySet()) { + String key = stringifiedAttribute.getKey(); + String value = stringifiedAttribute.getValue(); + typedAttributes.put(key, MPUtility.toNumberOrString(value)); + } + return typedAttributes; } return null; } @@ -584,7 +596,7 @@ public JSONObject getAllUserAttributesJson(long mpId) { if (entryValue == null) { entryValue = JSONObject.NULL; } - jsonAttributes.put(entry.getKey(), entryValue); + jsonAttributes.put(entry.getKey(), entryValue.toString()); } catch (JSONException e) { } @@ -597,10 +609,10 @@ public Map getUserAttributes(long mpId) { return getUserAttributes(null, mpId); } - public Map getUserAttributes(final UserAttributeListener listener, final long mpId) { + public Map getUserAttributes(final UserAttributeListenerWrapper listener, final long mpId) { Map allUserAttributes = new HashMap(); if (listener == null || Looper.getMainLooper() != Looper.myLooper()) { - Map userAttributes = getUserAttributeSingles(mpId); + Map userAttributes = getUserAttributeSingles(mpId); Map> userAttributeLists = getUserAttributeLists(mpId); if (listener != null) { listener.onUserAttributesReceived(userAttributes, userAttributeLists, mpId); @@ -618,7 +630,7 @@ public Map getUserAttributes(final UserAttributeListener listene instance.Internal().getMessageManager().getMessageHandler().post(new Runnable() { @Override public void run() { - final Map attributeSingles = getUserAttributeSingles(mpId); + final Map attributeSingles = getUserAttributeSingles(mpId); final Map> attributeLists = getUserAttributeLists(mpId); new Handler(Looper.getMainLooper()).post(new Runnable() { @Override @@ -660,9 +672,12 @@ public List setUserAttribute(UserAttributeResponse userAttrib } } if (userAttribute.attributeSingles != null) { - for (Map.Entry entry : userAttribute.attributeSingles.entrySet()) { + for (Map.Entry entry : userAttribute.attributeSingles.entrySet()) { String key = entry.getKey(); - String attributeValue = entry.getValue(); + String attributeValue = null; + if (entry.getValue() != null) { + attributeValue = entry.getValue().toString(); + } Object oldValue = currentValues.get(key); if (oldValue != null && oldValue instanceof String && ((String) oldValue).equalsIgnoreCase(attributeValue)) { continue; @@ -780,7 +795,7 @@ public static class UserAttributeRemoval { } public static class UserAttributeResponse { - public Map attributeSingles; + public Map attributeSingles; public Map> attributeLists; public long time; public long mpId; diff --git a/android-core/src/main/kotlin/com.mparticle/TypedUserAttributeListener.kt b/android-core/src/main/kotlin/com.mparticle/TypedUserAttributeListener.kt new file mode 100644 index 000000000..cc75b21d2 --- /dev/null +++ b/android-core/src/main/kotlin/com.mparticle/TypedUserAttributeListener.kt @@ -0,0 +1,9 @@ +package com.mparticle + +interface TypedUserAttributeListener: UserAttributeListenerType { + fun onUserAttributesReceived( + userAttributes: Map, + userAttributeLists: Map?>, + mpid: Long + ) +} \ No newline at end of file diff --git a/android-core/src/main/kotlin/com.mparticle/UserAttributeListenerType.kt b/android-core/src/main/kotlin/com.mparticle/UserAttributeListenerType.kt new file mode 100644 index 000000000..9ab4c8595 --- /dev/null +++ b/android-core/src/main/kotlin/com.mparticle/UserAttributeListenerType.kt @@ -0,0 +1,3 @@ +package com.mparticle + +interface UserAttributeListenerType diff --git a/android-core/src/main/kotlin/com/mparticle/identity/UserAttributeListenerWrapper.kt b/android-core/src/main/kotlin/com/mparticle/identity/UserAttributeListenerWrapper.kt new file mode 100644 index 000000000..691383ed7 --- /dev/null +++ b/android-core/src/main/kotlin/com/mparticle/identity/UserAttributeListenerWrapper.kt @@ -0,0 +1,17 @@ +package com.mparticle.identity + +import com.mparticle.TypedUserAttributeListener +import com.mparticle.UserAttributeListener +import com.mparticle.UserAttributeListenerType + +class UserAttributeListenerWrapper(val listener: UserAttributeListenerType) { + fun onUserAttributesReceived(singles: Map?, lists: Map?>?, mpid: Long?) { + when (listener) { + is UserAttributeListener -> (singles ?: mutableMapOf()) + .entries + .associate { it.key to it.value?.toString() } + .let { listener.onUserAttributesReceived(it, lists, mpid) } + is TypedUserAttributeListener -> mpid?.let { listener.onUserAttributesReceived(singles ?: mutableMapOf(), lists ?: mutableMapOf(), it) } + } + } +} \ No newline at end of file diff --git a/android-core/src/test/java/com/mparticle/identity/MParticleUserTest.java b/android-core/src/test/java/com/mparticle/identity/MParticleUserTest.java index 49cd99c4f..698febcc8 100644 --- a/android-core/src/test/java/com/mparticle/identity/MParticleUserTest.java +++ b/android-core/src/test/java/com/mparticle/identity/MParticleUserTest.java @@ -2,10 +2,8 @@ import com.mparticle.MParticle; import com.mparticle.MockMParticle; +import com.mparticle.TypedUserAttributeListener; import com.mparticle.UserAttributeListener; -import com.mparticle.consent.ConsentState; - -import junit.framework.Assert; import org.json.JSONArray; import org.json.JSONObject; @@ -22,6 +20,7 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotSame; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Matchers.eq; public class MParticleUserTest { @@ -107,10 +106,14 @@ public void testRemoveUserTag() throws Exception { @Test - public void testGetAllUserAttributes1() throws Exception { + public void testGetAllUserAttributes() throws Exception { UserAttributeListener listener = Mockito.mock(UserAttributeListener.class); id.getCurrentUser().getUserAttributes(listener); - Mockito.verify(mp.Identity().mMessageManager, Mockito.times(1)).getUserAttributes(listener, defaultMpId); + Mockito.verify(mp.Identity().mMessageManager, Mockito.times(1)).getUserAttributes(any(UserAttributeListenerWrapper.class), eq(defaultMpId)); + + TypedUserAttributeListener typedListener = Mockito.mock(TypedUserAttributeListener.class); + id.getCurrentUser().getUserAttributes(typedListener); + Mockito.verify(mp.Identity().mMessageManager, Mockito.times(2)).getUserAttributes(any(UserAttributeListenerWrapper.class), eq(defaultMpId)); } diff --git a/android-core/src/test/java/com/mparticle/internal/MPUtilityTest.java b/android-core/src/test/java/com/mparticle/internal/MPUtilityTest.java index 26c57a0b6..25058ac8e 100644 --- a/android-core/src/test/java/com/mparticle/internal/MPUtilityTest.java +++ b/android-core/src/test/java/com/mparticle/internal/MPUtilityTest.java @@ -231,4 +231,18 @@ private List toList(JSONArray jsonArray) { return list; } + @Test + public void testNumberDetection() { + assertEquals(12L, MPUtility.toNumberOrString("12")); + assertEquals(1.5, MPUtility.toNumberOrString("1.5")); + assertEquals(-1.5, MPUtility.toNumberOrString("-1.5")); + assertEquals(0L, MPUtility.toNumberOrString("0")); + //too big for a Long, should return a String + assertEquals(3.245987293478593E47, MPUtility.toNumberOrString("324598729347859283749857293487598237459872398475")); + assertEquals(3.245987293478593E46, MPUtility.toNumberOrString("32459872934785928374985729348759823745987239847.5")); + assertEquals("asdvasd", MPUtility.toNumberOrString("asdvasd")); + assertEquals("234sdvsda", MPUtility.toNumberOrString("234sdvsda")); + assertNull(MPUtility.toNumberOrString(null)); + } + } \ No newline at end of file diff --git a/android-kit-base/src/androidTest/kotlin/com/mparticle/kits/testkits/UserAttributeListenerTestKit.kt b/android-kit-base/src/androidTest/kotlin/com/mparticle/kits/testkits/UserAttributeListenerTestKit.kt index 8b821817b..e8869695c 100644 --- a/android-kit-base/src/androidTest/kotlin/com/mparticle/kits/testkits/UserAttributeListenerTestKit.kt +++ b/android-kit-base/src/androidTest/kotlin/com/mparticle/kits/testkits/UserAttributeListenerTestKit.kt @@ -5,7 +5,7 @@ import com.mparticle.kits.FilteredMParticleUser import com.mparticle.kits.KitIntegration open class UserAttributeListenerTestKit : ListenerTestKit(), KitIntegration.UserAttributeListener { - var onIncrementUserAttribute: ((key: String?, incrementedBy: Int, value: String?, user: FilteredMParticleUser?) -> Unit)? = null + var onIncrementUserAttribute: ((key: String?, incrementedBy: Number, value: String?, user: FilteredMParticleUser?) -> Unit)? = null var onRemoveUserAttribute: ((key: String?, user: FilteredMParticleUser?) -> Unit)? = null var onSetUserAttribute: ((key: String?, value: Any?, user: FilteredMParticleUser?) -> Unit)? = null var onSetUserTag: ((key: String?, user: FilteredMParticleUser?) -> Unit)? = null @@ -35,7 +35,7 @@ open class UserAttributeListenerTestKit : ListenerTestKit(), KitIntegration.User onUserReceived?.invoke(user) } - override fun onIncrementUserAttribute(key: String?, incrementedBy: Int, value: String?, user: FilteredMParticleUser?) { + override fun onIncrementUserAttribute(key: String?, incrementedBy: Number, value: String?, user: FilteredMParticleUser?) { onIncrementUserAttribute?.invoke(key, incrementedBy, value, user) onAttributeReceived?.invoke(key, value) onUserReceived?.invoke(user) diff --git a/android-kit-base/src/main/java/com/mparticle/kits/FilteredMParticleUser.java b/android-kit-base/src/main/java/com/mparticle/kits/FilteredMParticleUser.java index 3248f43bf..2ad09d2f6 100644 --- a/android-kit-base/src/main/java/com/mparticle/kits/FilteredMParticleUser.java +++ b/android-kit-base/src/main/java/com/mparticle/kits/FilteredMParticleUser.java @@ -1,10 +1,16 @@ package com.mparticle.kits; +import android.util.SparseBooleanArray; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.mparticle.MParticle; +import com.mparticle.TypedUserAttributeListener; import com.mparticle.UserAttributeListener; +import com.mparticle.UserAttributeListenerType; import com.mparticle.consent.ConsentState; import com.mparticle.identity.MParticleUser; -import com.mparticle.internal.KitManager; import java.util.HashMap; import java.util.List; @@ -62,21 +68,35 @@ public Map getUserAttributes() { } @Override - public Map getUserAttributes(final UserAttributeListener listener) { - return mpUser.getUserAttributes(new UserAttributeListener() { + public Map getUserAttributes(final UserAttributeListenerType listener) { + return mpUser.getUserAttributes(new TypedUserAttributeListener() { @Override - public void onUserAttributesReceived(Map userAttributes, Map> userAttributeLists, Long mpid) { + public void onUserAttributesReceived(@NonNull Map userAttributes, @NonNull Map> userAttributeLists, long mpid) { KitManagerImpl kitManager = provider.getKitManager(); if (kitManager != null) { userAttributes = kitManager.getDataplanFilter().transformUserAttributes(userAttributes); userAttributeLists = kitManager.getDataplanFilter().transformUserAttributes(userAttributeLists); } - listener.onUserAttributesReceived((Map)KitConfiguration.filterAttributes( - provider.getConfiguration().getUserAttributeFilters(), - userAttributes), - (Map>)KitConfiguration.filterAttributes( - provider.getConfiguration().getUserAttributeFilters(), - userAttributeLists), mpid); + SparseBooleanArray filters = provider.getConfiguration().getUserAttributeFilters(); + if (userAttributes == null) { + userAttributes = new HashMap<>(); + } + if (listener instanceof UserAttributeListener) { + Map stringifiedAttributes = new HashMap<>(); + for (Map.Entry entry: userAttributes.entrySet()) { + stringifiedAttributes.put(entry.getKey(), entry.getValue().toString()); + } + ((UserAttributeListener)listener).onUserAttributesReceived( + (Map) KitConfiguration.filterAttributes(filters, stringifiedAttributes), + (Map>) KitConfiguration.filterAttributes(filters, userAttributeLists), + mpid); + } + if (listener instanceof TypedUserAttributeListener) { + ((TypedUserAttributeListener)listener).onUserAttributesReceived( + KitConfiguration.filterAttributes(filters, userAttributes), + (Map>) KitConfiguration.filterAttributes(filters, userAttributeLists), + mpid); + } } }); } @@ -113,7 +133,7 @@ public boolean setUserAttributeList(String key, Object value) { } @Override - public boolean incrementUserAttribute(String key, int value) { + public boolean incrementUserAttribute(String key, Number value) { return false; } diff --git a/android-kit-base/src/main/java/com/mparticle/kits/KitIntegration.java b/android-kit-base/src/main/java/com/mparticle/kits/KitIntegration.java index 6524e19fd..4f4baf9a5 100644 --- a/android-kit-base/src/main/java/com/mparticle/kits/KitIntegration.java +++ b/android-kit-base/src/main/java/com/mparticle/kits/KitIntegration.java @@ -582,7 +582,7 @@ public interface IdentityListener { public interface UserAttributeListener { - void onIncrementUserAttribute (String key, int incrementedBy, String value, FilteredMParticleUser user); + void onIncrementUserAttribute (String key, Number incrementedBy, String value, FilteredMParticleUser user); void onRemoveUserAttribute(String key, FilteredMParticleUser user); diff --git a/android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java b/android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java index fc066c99d..fa710cabe 100644 --- a/android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java +++ b/android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java @@ -711,7 +711,7 @@ public void removeUserAttribute(String key, long mpid) { } @Override - public void incrementUserAttribute(String key, int incrementedBy, String newValue, long mpid) { + public void incrementUserAttribute(String key, Number incrementedBy, String newValue, long mpid) { if (mDataplanFilter.isUserAttributeBlocked(key)) { return; } diff --git a/kits/appboy-kit b/kits/appboy-kit index 84171ed5a..defa7e898 160000 --- a/kits/appboy-kit +++ b/kits/appboy-kit @@ -1 +1 @@ -Subproject commit 84171ed5addbefdbfdcb843d980a8a47774d73f3 +Subproject commit defa7e898ac965fba6da6222dc4649f575436d7c diff --git a/kits/clevertap-kit b/kits/clevertap-kit index f1eff2c11..75bc6f162 160000 --- a/kits/clevertap-kit +++ b/kits/clevertap-kit @@ -1 +1 @@ -Subproject commit f1eff2c115de5aa4fc6f6ab892a8a78a2a67a307 +Subproject commit 75bc6f1626991901c4eb13ca7db224906c08215d diff --git a/kits/googleanalyticsfirebase-kit b/kits/googleanalyticsfirebase-kit index cc5351de5..dfdfbac9b 160000 --- a/kits/googleanalyticsfirebase-kit +++ b/kits/googleanalyticsfirebase-kit @@ -1 +1 @@ -Subproject commit cc5351de58b6964a893e64ea0095c20f9f1ada06 +Subproject commit dfdfbac9bc6605410827c9e7812889ef75dc02aa diff --git a/kits/googleanalyticsfirebasega4-kit b/kits/googleanalyticsfirebasega4-kit index e5546b71d..d4d5cbdaf 160000 --- a/kits/googleanalyticsfirebasega4-kit +++ b/kits/googleanalyticsfirebasega4-kit @@ -1 +1 @@ -Subproject commit e5546b71d262e62ab02a1283483e4a34b9941b97 +Subproject commit d4d5cbdaf3a77b98542c97005ca25713034cae84 diff --git a/kits/leanplum-kit b/kits/leanplum-kit index 09c3a683a..32210c6ff 160000 --- a/kits/leanplum-kit +++ b/kits/leanplum-kit @@ -1 +1 @@ -Subproject commit 09c3a683a381d435728f8573ca7f4db3e719269c +Subproject commit 32210c6ff017a72b658c19568eff49141b2a89d2 diff --git a/kits/optimizely-kit b/kits/optimizely-kit index 12ba02a4a..47ffeabd7 160000 --- a/kits/optimizely-kit +++ b/kits/optimizely-kit @@ -1 +1 @@ -Subproject commit 12ba02a4a23c3053d4fc96c5d3da9c7e8cdf6d54 +Subproject commit 47ffeabd7a9b04bc206b5160ba3456ab1632ad01 diff --git a/kits/pilgrim-kit b/kits/pilgrim-kit index 67b70e965..8b5098dd7 160000 --- a/kits/pilgrim-kit +++ b/kits/pilgrim-kit @@ -1 +1 @@ -Subproject commit 67b70e9656b5f23a9642bdaecfb559c7b62a85cd +Subproject commit 8b5098dd72bddec2b86c75d9f49b9e647d32fcb9 diff --git a/kits/singular-kit b/kits/singular-kit index 3785e17f3..50d5797a1 160000 --- a/kits/singular-kit +++ b/kits/singular-kit @@ -1 +1 @@ -Subproject commit 3785e17f315e525091f083115fa7580ac0a1f714 +Subproject commit 50d5797a1c8e870c7f900e7c459bdc25f43277a8 diff --git a/testutils/src/main/java/com/mparticle/mock/AbstractMParticleUser.java b/testutils/src/main/java/com/mparticle/mock/AbstractMParticleUser.java index 606a352a9..678c8d86a 100644 --- a/testutils/src/main/java/com/mparticle/mock/AbstractMParticleUser.java +++ b/testutils/src/main/java/com/mparticle/mock/AbstractMParticleUser.java @@ -5,6 +5,7 @@ import com.mparticle.MParticle; import com.mparticle.UserAttributeListener; +import com.mparticle.UserAttributeListenerType; import com.mparticle.consent.ConsentState; import com.mparticle.identity.MParticleUser; @@ -24,7 +25,7 @@ public Map getUserAttributes() { @Nullable @Override - public Map getUserAttributes(@Nullable UserAttributeListener listener) { + public Map getUserAttributes(@Nullable UserAttributeListenerType listener) { return null; } @@ -49,7 +50,7 @@ public boolean setUserAttributeList(@NonNull String key, @NonNull Object value) } @Override - public boolean incrementUserAttribute(@NonNull String key, int value) { + public boolean incrementUserAttribute(@NonNull String key, Number value) { return false; }