From 80de8ea51253f73b04947c388d887b3ec4a05f6d Mon Sep 17 00:00:00 2001 From: markvdouw Date: Thu, 20 Jul 2023 17:09:10 -0300 Subject: [PATCH] feat: sideloading kits (#401) * feat: sideloading kits (#378) * Adding local kit as a wrapper for config and kit integration. Creating empty config based on a random unused Int generated by the factory. Adding kit merging and initializing. * Changes on tests for compatibility * Changes due to comments Adding unit test to check getting config from KitIntegration wrapper set in MParticleOptions, KitManagerFactory creating instances, applying config, etc. * Redoing sideloaded kits * Changes into dependencies * Combining sideloaded kits into prefs for merge. * Adding capabilities for integration and merging sideloaded kits with remote config. * Fixing unit tests * # Conflicts: # android-core/src/main/java/com/mparticle/internal/ConfigManager.java * ktlintFormat * Test fixes * Fixing incorrect HTML tag * Changes due to comments * Change due to comments * Modification of long proguard * ktlintFormat * Changes due to team discussion. Using kit instance and combine it with created instances. Support for multiple instances. Adding condition to filter sideloaded kits with id < 1000000 * Implementing default functions from KitIntegration * Test fix --------- Signed-off-by: markvdouw --- android-core/proguard.pro | 2 + .../internal/ConfigMigrationTest.kt | 11 +- .../internal/ConfigStalenessCheckTest.kt | 5 +- .../main/java/com/mparticle/MParticle.java | 6 +- .../java/com/mparticle/MParticleOptions.java | 35 +- .../com/mparticle/internal/ConfigManager.java | 43 +- .../com/mparticle/internal/Constants.java | 1 + .../mparticle/internal/DeviceAttributes.java | 41 +- .../internal/MParticleApiClientImpl.java | 1 + .../com/mparticle/internal/SideloadedKit.kt | 12 + .../mparticle/internal/SideloadedKitsUtils.kt | 30 ++ .../mparticle/internal/ConfigManagerTest.kt | 9 +- .../internal/DeviceAttributesTest.kt | 2 +- .../com/mparticle/internal/LoggerTest.kt | 1 + .../mparticle/internal/MessageBatchTest.kt | 2 + android-kit-base/proguard.pro | 14 + .../com/mparticle/kits/KitConfiguration.java | 372 +++++++++++++++++- .../com/mparticle/kits/KitIntegration.java | 27 +- .../mparticle/kits/KitIntegrationFactory.java | 123 +++--- .../com/mparticle/kits/KitManagerImpl.java | 60 +-- .../com/mparticle/kits/MPSideloadedFilters.kt | 9 + .../com/mparticle/kits/MPSideloadedKit.kt | 43 ++ .../kits/mappings/CustomMapping.java | 67 ++-- .../com/mparticle/kits/KitManagerImplTest.kt | 125 ++++++ .../com/mparticle/kits/KitManagerTest.kt | 20 +- .../com/mparticle/mock/MockConfigManager.java | 2 +- .../mock/MockKitIntegrationFactory.java | 5 + .../mparticle/mock/MockKitManagerImpl.java | 6 + 28 files changed, 897 insertions(+), 177 deletions(-) create mode 100644 android-core/src/main/java/com/mparticle/internal/SideloadedKit.kt create mode 100644 android-core/src/main/java/com/mparticle/internal/SideloadedKitsUtils.kt create mode 100644 android-kit-base/proguard.pro create mode 100644 android-kit-base/src/main/java/com/mparticle/kits/MPSideloadedFilters.kt create mode 100644 android-kit-base/src/main/java/com/mparticle/kits/MPSideloadedKit.kt diff --git a/android-core/proguard.pro b/android-core/proguard.pro index 208c2a6e5..f55779de1 100644 --- a/android-core/proguard.pro +++ b/android-core/proguard.pro @@ -123,6 +123,8 @@ -keep class com.mparticle.consent.CCPAConsent$Builder {*;} +-keep class com.mparticle.internal.SideloadedKit { *; } + -keep class com.mparticle.internal.KitManager { *; } -keep class com.mparticle.internal.KitManager$* { *; } -keep class com.mparticle.internal.CoreCallbacks { *; } diff --git a/android-core/src/androidTest/kotlin/com.mparticle/internal/ConfigMigrationTest.kt b/android-core/src/androidTest/kotlin/com.mparticle/internal/ConfigMigrationTest.kt index 24e9af59c..eef4c14e8 100644 --- a/android-core/src/androidTest/kotlin/com.mparticle/internal/ConfigMigrationTest.kt +++ b/android-core/src/androidTest/kotlin/com.mparticle/internal/ConfigMigrationTest.kt @@ -3,10 +3,12 @@ package com.mparticle.internal import com.mparticle.MParticle import com.mparticle.MParticleOptions import com.mparticle.testutils.BaseCleanInstallEachTest +import org.json.JSONArray import org.json.JSONObject import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertNull +import kotlin.test.assertTrue class ConfigMigrationTest : BaseCleanInstallEachTest() { private val oldConfigSharedprefsKey = "json" @@ -81,7 +83,7 @@ class ConfigMigrationTest : BaseCleanInstallEachTest() { // make sure config is deleted MParticle.getInstance()!!.Internal().configManager.let { - assertNull(it.config) + assertTrue(it.config?.isEmpty()!!) assertNull(it.configTimestamp) assertNull(it.etag) assertNull(it.ifModified) @@ -127,13 +129,12 @@ class ConfigMigrationTest : BaseCleanInstallEachTest() { } private fun assertOldConfigState(config: JSONObject) { - assertNull(ConfigManager.getInstance(mContext).getKitConfigPreferences().getString(ConfigManager.KIT_CONFIG_KEY, null)) - assertEquals(config.toString(), ConfigManager.getPreferences(mContext).getString(oldConfigSharedprefsKey, null)) + assertEquals(config.toString(), ConfigManager.getPreferences(mContext).getString(oldConfigSharedprefsKey, JSONArray().toString())) } private fun assertNewConfigState(config: JSONObject) { - val configString = ConfigManager.getPreferences(mContext).getString(ConfigManager.CONFIG_JSON, null) + val configString = ConfigManager.getPreferences(mContext).getString(ConfigManager.CONFIG_JSON, JSONArray().toString()) assertNull(JSONObject(configString).optJSONArray(ConfigManager.KEY_EMBEDDED_KITS)) - assertEquals(config.optString(ConfigManager.KEY_EMBEDDED_KITS, null), ConfigManager.getInstance(mContext).kitConfigPreferences.getString(ConfigManager.KIT_CONFIG_KEY, null)) + assertEquals(config.optString(ConfigManager.KEY_EMBEDDED_KITS, JSONArray().toString()), ConfigManager.getInstance(mContext).kitConfigPreferences.getString(ConfigManager.KIT_CONFIG_KEY, JSONArray().toString())) } } diff --git a/android-core/src/androidTest/kotlin/com.mparticle/internal/ConfigStalenessCheckTest.kt b/android-core/src/androidTest/kotlin/com.mparticle/internal/ConfigStalenessCheckTest.kt index 903dc8c1a..907ec80ea 100644 --- a/android-core/src/androidTest/kotlin/com.mparticle/internal/ConfigStalenessCheckTest.kt +++ b/android-core/src/androidTest/kotlin/com.mparticle/internal/ConfigStalenessCheckTest.kt @@ -9,6 +9,7 @@ import org.junit.Test import kotlin.test.assertEquals import kotlin.test.assertNotNull import kotlin.test.assertNull +import kotlin.test.assertTrue class ConfigStalenessCheckTest : BaseCleanInstallEachTest() { val configManager: ConfigManager @@ -82,7 +83,7 @@ class ConfigStalenessCheckTest : BaseCleanInstallEachTest() { latch = MPLatch(1) // after configMaxAge time has elapsed, config should be cleared after restart - assertNull(configManager.config) + assertTrue(configManager.config?.isEmpty()!!) assertNull(configManager.etag) assertNull(configManager.ifModified) assertNull(configManager.configTimestamp) @@ -161,7 +162,7 @@ class ConfigStalenessCheckTest : BaseCleanInstallEachTest() { latch = MPLatch(1) // directly after restart, config should be cleared - assertNull(configManager.config) + assertTrue(configManager.config?.isEmpty()!!) assertNull(configManager.etag) assertNull(configManager.ifModified) assertNull(configManager.configTimestamp) diff --git a/android-core/src/main/java/com/mparticle/MParticle.java b/android-core/src/main/java/com/mparticle/MParticle.java index 99f8d6a7b..726c852d4 100644 --- a/android-core/src/main/java/com/mparticle/MParticle.java +++ b/android-core/src/main/java/com/mparticle/MParticle.java @@ -147,7 +147,7 @@ private MParticle(MParticleOptions options) { */ public void setUpdateInterval(int interval) { long intervalMillis = interval * 1000L; - if ( (intervalMillis >= 1 && mConfigManager.getUploadInterval() != intervalMillis)) { + if ((intervalMillis >= 1 && mConfigManager.getUploadInterval() != intervalMillis)) { upload(); mConfigManager.setUploadInterval(interval); } @@ -293,6 +293,7 @@ public static boolean isAndroidIdEnabled() { /** * Returns the wrapper sdk version + * * @return */ public WrapperSdkVersion getWrapperSdkVersion() { @@ -302,6 +303,7 @@ public WrapperSdkVersion getWrapperSdkVersion() { /** * Set wrapper sdk, by default NONE. * This can only be set once. + * * @param wrapperSdk diffent from {@link WrapperSdk.NONE} * @param version */ @@ -1508,7 +1510,7 @@ public void onUserIdentified(MParticleUser user, MParticleUser previousUser) { if (user != null) { Identity().removeIdentityStateListener(this); Logger.verbose("Sending previously deferred logPushRegistration Modify request."); - sendPushTokenModifyRequest(user, newInstanceId, oldInstanceId); + sendPushTokenModifyRequest(user, newInstanceId, oldInstanceId); } } }; diff --git a/android-core/src/main/java/com/mparticle/MParticleOptions.java b/android-core/src/main/java/com/mparticle/MParticleOptions.java index f539447dd..286a9eaa2 100644 --- a/android-core/src/main/java/com/mparticle/MParticleOptions.java +++ b/android-core/src/main/java/com/mparticle/MParticleOptions.java @@ -12,10 +12,10 @@ import com.mparticle.internal.Logger; import com.mparticle.internal.MPUtility; import com.mparticle.internal.PushRegistrationHelper; +import com.mparticle.internal.SideloadedKit; import com.mparticle.networking.NetworkOptions; import com.mparticle.networking.NetworkOptionsManager; -import org.jetbrains.annotations.NotNull; import org.json.JSONException; import org.json.JSONObject; @@ -54,6 +54,7 @@ public class MParticleOptions { private MParticle.OperatingSystem mOperatingSystem = MParticle.OperatingSystem.ANDROID; private DataplanOptions mDataplanOptions; private Map> mConfigurations = new HashMap(); + private List sideloadedKits = new ArrayList<>(); private MParticleOptions() { } @@ -145,6 +146,7 @@ public MParticleOptions(@NonNull Builder builder) { this.mDataplanVersion = builder.dataplanVersion; this.mDataplanOptions = builder.dataplanOptions; this.mConfigurations = builder.configurations; + this.sideloadedKits = builder.sideloadedKits; } /** @@ -179,6 +181,16 @@ public MParticle.Environment getEnvironment() { return mEnvironment; } + /** + * Get list of sideloadedKits kits + * + * @return + */ + @NonNull + public List getSideloadedKits() { + return sideloadedKits; + } + /** * Query the API Key. * @@ -387,6 +399,7 @@ public static class Builder { private DataplanOptions dataplanOptions; private Map> configurations = new HashMap(); private boolean isAppDebuggable; + private List sideloadedKits = new ArrayList<>(); private Builder(Context context) { this.context = context; @@ -421,6 +434,26 @@ public Builder installType(@NonNull MParticle.InstallType installType) { return this; } + /** + * Add sideloaded kits + * + * @param kits + * @return + */ + @NonNull + public Builder sideloadedKits(@NonNull List kits) { + List _kits = new ArrayList<>(); + for (SideloadedKit kit : kits) { + if (kit.kitId() < 1000000) { + Logger.error("Sideloaded kit " + kit.getName() + " must have a kitId greater or equal than 1000000, current one is " + kit.kitId() + " and will not be included."); + } else { + _kits.add(kit); + } + } + this.sideloadedKits = _kits; + return this; + } + /** * Indicate a known {@link com.mparticle.MParticle.Environment} the Application will be running in. If this method is not used. * a default Environment of MParticle.Environment.AutoDetect will be used. diff --git a/android-core/src/main/java/com/mparticle/internal/ConfigManager.java b/android-core/src/main/java/com/mparticle/internal/ConfigManager.java index 7a9a60d9f..cf225f843 100644 --- a/android-core/src/main/java/com/mparticle/internal/ConfigManager.java +++ b/android-core/src/main/java/com/mparticle/internal/ConfigManager.java @@ -70,7 +70,7 @@ public class ConfigManager { static final String DATAPLAN_BLOCK_EVENT_ATTRIBUTES = "ea"; static final String DATAPLAN_BLOCK_USER_ATTRIBUTES = "ua"; static final String DATAPLAN_BLOCK_USER_IDENTITIES = "id"; - static final String KIT_CONFIG_KEY = "kit_config"; + public static final String KIT_CONFIG_KEY = "kit_config"; static final String MIGRATED_TO_KIT_SHARED_PREFS = "is_mig_kit_sp"; private static final int DEVMODE_UPLOAD_INTERVAL_MILLISECONDS = 10 * 1000; @@ -106,6 +106,7 @@ public class ConfigManager { public static final int DEFAULT_SESSION_TIMEOUT_SECONDS = 60; public static final int DEFAULT_UPLOAD_INTERVAL = 600; private List configUpdatedListeners = new ArrayList<>(); + private List sideloadedKits = new ArrayList<>(); private ConfigManager() { super(); @@ -129,10 +130,10 @@ public ConfigManager(Context context) { } public ConfigManager(@NonNull MParticleOptions options) { - this(options.getContext(), options.getEnvironment(), options.getApiKey(), options.getApiSecret(), options.getDataplanOptions(), options.getDataplanId(), options.getDataplanVersion(), options.getConfigMaxAge(), options.getConfigurationsForTarget(ConfigManager.class)); + this(options.getContext(), options.getEnvironment(), options.getApiKey(), options.getApiSecret(), options.getDataplanOptions(), options.getDataplanId(), options.getDataplanVersion(), options.getConfigMaxAge(), options.getConfigurationsForTarget(ConfigManager.class), options.getSideloadedKits()); } - public ConfigManager(@NonNull Context context, @Nullable MParticle.Environment environment, @Nullable String apiKey, @Nullable String apiSecret, @Nullable MParticleOptions.DataplanOptions dataplanOptions, @Nullable String dataplanId, @Nullable Integer dataplanVersion, @Nullable Integer configMaxAge, @Nullable List> configurations) { + public ConfigManager(@NonNull Context context, @Nullable MParticle.Environment environment, @Nullable String apiKey, @Nullable String apiSecret, @Nullable MParticleOptions.DataplanOptions dataplanOptions, @Nullable String dataplanId, @Nullable Integer dataplanVersion, @Nullable Integer configMaxAge, @Nullable List> configurations, @Nullable List sideloadedKits) { mContext = context.getApplicationContext(); sPreferences = getPreferences(mContext); if (apiKey != null || apiSecret != null) { @@ -148,6 +149,11 @@ public ConfigManager(@NonNull Context context, @Nullable MParticle.Environment e mDataplanVersion = dataplanVersion; mDataplanId = dataplanId; mMaxConfigAge = configMaxAge; + if (sideloadedKits != null) { + this.sideloadedKits = sideloadedKits; + } else { + this.sideloadedKits = new ArrayList<>(); + } if (configurations != null) { for (Configuration configuration : configurations) { configuration.apply(this); @@ -277,7 +283,7 @@ void checkConfigStaleness() { @Nullable String getConfig() { - return sPreferences.getString(CONFIG_JSON, null); + return sPreferences.getString(CONFIG_JSON, ""); } void setConfigTimestamp(Long timestamp) { @@ -321,7 +327,7 @@ void saveConfigJson(JSONObject coreConfig, JSONArray kitConfig, String etag, Str .apply(); getKitConfigPreferences() .edit() - .putString(KIT_CONFIG_KEY, kitConfigString) + .putString(KIT_CONFIG_KEY, SideloadedKitsUtils.INSTANCE.combineConfig(kitConfig, sideloadedKits).toString()) .apply(); } else { Logger.debug("clearing current configurations"); @@ -346,7 +352,26 @@ public synchronized void updateConfig(JSONObject responseJSON) throws JSONExcept updateConfig(responseJSON, null, null); } - public synchronized void updateConfig(JSONObject responseJSON, String etag, String lastModified) throws JSONException { + public synchronized void configUpToDate() throws JSONException { + try { + String config = getKitConfigPreferences().getString(KIT_CONFIG_KEY, ""); + if (!config.isEmpty()) { + JSONArray kitConfig = new JSONArray(config); + JSONArray combined = SideloadedKitsUtils.INSTANCE.combineConfig(kitConfig, sideloadedKits); + getKitConfigPreferences() + .edit() + .putString(KIT_CONFIG_KEY, combined.toString()) + .apply(); + onConfigLoaded(ConfigType.KIT, kitConfig != combined); + } + } catch (JSONException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + public synchronized void updateConfig(JSONObject responseJSON, String etag, String + lastModified) throws JSONException { if (responseJSON == null) { responseJSON = new JSONObject(); } @@ -369,7 +394,8 @@ private synchronized void updateKitConfig(@Nullable JSONArray kitConfigs) { } } - private synchronized void updateCoreConfig(JSONObject responseJSON, boolean newConfig) throws JSONException { + private synchronized void updateCoreConfig(JSONObject responseJSON, boolean newConfig) throws + JSONException { SharedPreferences.Editor editor = sPreferences.edit(); if (responseJSON.has(KEY_UNHANDLED_EXCEPTIONS)) { mLogUnhandledExceptions = responseJSON.getString(KEY_UNHANDLED_EXCEPTIONS); @@ -673,7 +699,8 @@ public void setPushRegistration(PushRegistrationHelper.PushRegistration pushRegi } } - public void setPushRegistrationInBackground(PushRegistrationHelper.PushRegistration pushRegistration) { + public void setPushRegistrationInBackground(PushRegistrationHelper.PushRegistration + pushRegistration) { String oldInstanceId = getPushInstanceId(); if (oldInstanceId == null) { oldInstanceId = ""; diff --git a/android-core/src/main/java/com/mparticle/internal/Constants.java b/android-core/src/main/java/com/mparticle/internal/Constants.java index eb60ae65a..2238d489f 100644 --- a/android-core/src/main/java/com/mparticle/internal/Constants.java +++ b/android-core/src/main/java/com/mparticle/internal/Constants.java @@ -221,6 +221,7 @@ public interface MessageKey { String LAUNCH_COUNT = "lc"; String LAUNCH_COUNT_SINCE_UPGRADE = "lcu"; String LAST_USE_DATE = "lud"; + String SIDELOADED_KITS_COUNT = "sideloaded_kits_count"; // device customAttributes String BUILD_ID = "bid"; String BRAND = "b"; diff --git a/android-core/src/main/java/com/mparticle/internal/DeviceAttributes.java b/android-core/src/main/java/com/mparticle/internal/DeviceAttributes.java index 845f8b598..7948a3f4f 100644 --- a/android-core/src/main/java/com/mparticle/internal/DeviceAttributes.java +++ b/android-core/src/main/java/com/mparticle/internal/DeviceAttributes.java @@ -18,6 +18,7 @@ import org.json.JSONObject; import java.util.Locale; +import java.util.Set; import java.util.TimeZone; public class DeviceAttributes { @@ -30,7 +31,10 @@ public class DeviceAttributes { private boolean firstCollection = true; private MParticle.OperatingSystem operatingSystem; - /** package-private **/ DeviceAttributes(MParticle.OperatingSystem operatingSystem) { + /** + * package-private + **/ + DeviceAttributes(MParticle.OperatingSystem operatingSystem) { this.operatingSystem = operatingSystem; } @@ -42,9 +46,25 @@ public static String getDeviceImei() { return deviceImei; } + private int getSideloadedKitsCount() { + try { + Set kits = MParticle.getInstance().Internal().getKitManager().getSupportedKits(); + int count = 0; + for (Integer kitId : kits) { + if (kitId >= 1000000) { + count++; + } + } + return count; + } catch (Exception e) { + Logger.debug("Exception while adding sideloadedKitsCount to Device Attribute"); + return 0; + } + } + /** * Generates a collection of application attributes that will not change during an app's process. - * + *

* This contains logic that MUST only be called once per app run. * * @param appContext the application context @@ -59,12 +79,14 @@ public JSONObject getStaticApplicationInfo(Context appContext) { PackageManager packageManager = appContext.getPackageManager(); String packageName = appContext.getPackageName(); attributes.put(MessageKey.APP_PACKAGE_NAME, packageName); + attributes.put(MessageKey.SIDELOADED_KITS_COUNT, getSideloadedKitsCount()); String versionCode = UNKNOWN; try { PackageInfo pInfo = appContext.getPackageManager().getPackageInfo(packageName, 0); versionCode = Integer.toString(pInfo.versionCode); attributes.put(MessageKey.APP_VERSION, pInfo.versionName); - } catch (PackageManager.NameNotFoundException nnfe) { } + } catch (PackageManager.NameNotFoundException nnfe) { + } attributes.put(MessageKey.APP_VERSION_CODE, versionCode); @@ -101,7 +123,7 @@ public JSONObject getStaticApplicationInfo(Context appContext) { int countSinceUpgrade = userStorage.getLaunchesSinceUpgrade(); long upgradeDate = preferences.getLong(PrefKeys.UPGRADE_DATE, now); - if (persistedVersion < 0 || persistedVersion != pInfo.versionCode){ + if (persistedVersion < 0 || persistedVersion != pInfo.versionCode) { countSinceUpgrade = 0; upgradeDate = now; editor.putInt(PrefKeys.COUNTER_VERSION, pInfo.versionCode); @@ -137,8 +159,7 @@ void updateInstallReferrer(Context context, JSONObject attributes) { SharedPreferences preferences = context.getSharedPreferences(Constants.PREFS_FILE, Context.MODE_PRIVATE); try { attributes.put(MessageKey.INSTALL_REFERRER, preferences.getString(Constants.PrefKeys.INSTALL_REFERRER, null)); - } - catch (JSONException ignored) { + } catch (JSONException ignored) { // this, hopefully, should never fail } } @@ -154,7 +175,7 @@ public static void addAndroidId(JSONObject attributes, Context context) throws J /** * Generates a collection of device attributes that will not change during an app's process. - * + *

* This contains logic that MUST only be called once per app run. * * @param appContext the application context @@ -255,7 +276,7 @@ public void updateDeviceInfo(Context context, JSONObject deviceInfo) { MParticle instance = MParticle.getInstance(); //check instance nullability here and decline to act if it is not available. Don't want to have the case where we are overriding isLimiAdTrackingEnabled //just because there was a timing issue with the singleton - if (instance != null) { + if (instance != null) { if (adIdInfo.isLimitAdTrackingEnabled) { message = adIdInfo.advertiser.descriptiveName + " Advertising ID tracking is disabled on this device."; } else { @@ -294,8 +315,8 @@ public void updateDeviceInfo(Context context, JSONObject deviceInfo) { } } - public JSONObject getDeviceInfo(Context context){ - if (deviceInfo == null){ + public JSONObject getDeviceInfo(Context context) { + if (deviceInfo == null) { deviceInfo = getStaticDeviceInfo(context); } updateDeviceInfo(context, deviceInfo); diff --git a/android-core/src/main/java/com/mparticle/internal/MParticleApiClientImpl.java b/android-core/src/main/java/com/mparticle/internal/MParticleApiClientImpl.java index 609360579..0487d6a96 100644 --- a/android-core/src/main/java/com/mparticle/internal/MParticleApiClientImpl.java +++ b/android-core/src/main/java/com/mparticle/internal/MParticleApiClientImpl.java @@ -186,6 +186,7 @@ public void fetchConfig(boolean force) throws IOException, MPConfigException { } else if (connection.getResponseCode() == 400) { throw new MPConfigException(); } else if (connection.getResponseCode() == 304) { + mConfigManager.configUpToDate(); Logger.verbose("Config request deferred, configuration already up-to-date."); } else { Logger.error("Config request failed- " + connection.getResponseCode() + ": " + connection.getResponseMessage()); diff --git a/android-core/src/main/java/com/mparticle/internal/SideloadedKit.kt b/android-core/src/main/java/com/mparticle/internal/SideloadedKit.kt new file mode 100644 index 000000000..54021580d --- /dev/null +++ b/android-core/src/main/java/com/mparticle/internal/SideloadedKit.kt @@ -0,0 +1,12 @@ +package com.mparticle.internal + +import org.json.JSONObject + +interface SideloadedKit { + + fun getJsonConfig(): JSONObject? + + fun kitId(): Int + + fun getName(): String +} diff --git a/android-core/src/main/java/com/mparticle/internal/SideloadedKitsUtils.kt b/android-core/src/main/java/com/mparticle/internal/SideloadedKitsUtils.kt new file mode 100644 index 000000000..705dc9e91 --- /dev/null +++ b/android-core/src/main/java/com/mparticle/internal/SideloadedKitsUtils.kt @@ -0,0 +1,30 @@ +package com.mparticle.internal + +import org.json.JSONArray + +object SideloadedKitsUtils { + + fun combineConfig(kitConfig: JSONArray?, kits: List): JSONArray { + var results = JSONArray() + var addedIds = mutableSetOf() + kitConfig?.let { kitConfig -> + for (i in 0 until kitConfig.length()) { + val kit = kitConfig.getJSONObject(i) + val id = kit.optInt("id", -1) + if (id != -1 && id < 1000000 && !addedIds.contains(id)) { + results.put(kit) + addedIds.add(id) + } + } + } + kits.forEach { kit -> + if (!addedIds.contains(kit.kitId())) { + kit.getJsonConfig()?.let { + results.put(it) + addedIds.add(kit.kitId()) + } + } + } + return results + } +} diff --git a/android-core/src/test/kotlin/com/mparticle/internal/ConfigManagerTest.kt b/android-core/src/test/kotlin/com/mparticle/internal/ConfigManagerTest.kt index 6030278c8..6b58b25f5 100644 --- a/android-core/src/test/kotlin/com/mparticle/internal/ConfigManagerTest.kt +++ b/android-core/src/test/kotlin/com/mparticle/internal/ConfigManagerTest.kt @@ -38,6 +38,7 @@ class ConfigManagerTest { null, null, null, + null, null ) mockMp = MockMParticle() @@ -56,6 +57,7 @@ class ConfigManagerTest { null, null, null, + null, null ) Assert.assertEquals("key1", manager.apiKey) @@ -78,6 +80,7 @@ class ConfigManagerTest { null, null, null, + null, null ) Assert.assertEquals("key2", manager.apiKey) @@ -615,6 +618,7 @@ class ConfigManagerTest { null, null, null, + null, null ) val newEtag = RandomUtils().getAlphaString(24) @@ -638,6 +642,7 @@ class ConfigManagerTest { null, null, null, + null, null ) val lastModified = abs(ran.nextLong()).toString() @@ -690,14 +695,14 @@ class ConfigManagerTest { } // test defaults - Assert.assertNull(manager.config) + Assert.assertTrue(manager.config?.isEmpty()!!) Assert.assertNull(manager.etag) Assert.assertNull(manager.ifModified) Assert.assertNull(manager.configTimestamp) // test reload() does not set config manager.reloadCoreConfig(newConfigJson) - Assert.assertNull(manager.config) + Assert.assertTrue(manager.config?.isEmpty()!!) Assert.assertNull(manager.etag) Assert.assertNull(manager.ifModified) Assert.assertNull(manager.configTimestamp) diff --git a/android-core/src/test/kotlin/com/mparticle/internal/DeviceAttributesTest.kt b/android-core/src/test/kotlin/com/mparticle/internal/DeviceAttributesTest.kt index 703d1ba77..38e8d24ce 100644 --- a/android-core/src/test/kotlin/com/mparticle/internal/DeviceAttributesTest.kt +++ b/android-core/src/test/kotlin/com/mparticle/internal/DeviceAttributesTest.kt @@ -57,7 +57,7 @@ class DeviceAttributesTest { fun testAppInfoLaunchCount() { val context: Context = MockContext() // Clear out the stored data for the current user, so we don't get any launches from previous tests. - ConfigManager(context, null, null, null, null, null, null, null, null).deleteUserStorage( + ConfigManager(context, null, null, null, null, null, null, null, null, null).deleteUserStorage( context, ConfigManager.getMpid(context) ) diff --git a/android-core/src/test/kotlin/com/mparticle/internal/LoggerTest.kt b/android-core/src/test/kotlin/com/mparticle/internal/LoggerTest.kt index 041d731dc..311890673 100644 --- a/android-core/src/test/kotlin/com/mparticle/internal/LoggerTest.kt +++ b/android-core/src/test/kotlin/com/mparticle/internal/LoggerTest.kt @@ -50,6 +50,7 @@ class LoggerTest { null, null, null, + null, null ) Assert.assertNotNull(Logger.getLogHandler()) diff --git a/android-core/src/test/kotlin/com/mparticle/internal/MessageBatchTest.kt b/android-core/src/test/kotlin/com/mparticle/internal/MessageBatchTest.kt index 13e5717db..e87234848 100644 --- a/android-core/src/test/kotlin/com/mparticle/internal/MessageBatchTest.kt +++ b/android-core/src/test/kotlin/com/mparticle/internal/MessageBatchTest.kt @@ -27,6 +27,7 @@ class MessageBatchTest { null, null, null, + null, null ) var sessionHistory = true @@ -75,6 +76,7 @@ class MessageBatchTest { null, null, null, + null, null ) val sessionHistory = true diff --git a/android-kit-base/proguard.pro b/android-kit-base/proguard.pro new file mode 100644 index 000000000..0f5a9a6c7 --- /dev/null +++ b/android-kit-base/proguard.pro @@ -0,0 +1,14 @@ + +# For native methods, see http://proguard.sourceforge.net/manual/examples.html#native +-keepclasseswithmembernames class * { + native ; +} + +-keepparameternames +-renamesourcefileattribute SourceFile +-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,*Annotation*,EnclosingMethod +-repackageclasses com.mparticle + +-keep class com.mparticle.kits.MPSideloadedKit { *; } +-keep class com.mparticle.kits.MPSideloadedFilters { *; } + diff --git a/android-kit-base/src/main/java/com/mparticle/kits/KitConfiguration.java b/android-kit-base/src/main/java/com/mparticle/kits/KitConfiguration.java index 59e4307e9..f28483f4f 100644 --- a/android-kit-base/src/main/java/com/mparticle/kits/KitConfiguration.java +++ b/android-kit-base/src/main/java/com/mparticle/kits/KitConfiguration.java @@ -33,20 +33,20 @@ public class KitConfiguration { private final static int ENTITY_PRODUCT = 1; private final static int ENTITY_PROMOTION = 2; final static String KEY_ID = "id"; - private final static String KEY_ATTRIBUTE_VALUE_FILTERING = "avf"; + final static String KEY_ATTRIBUTE_VALUE_FILTERING = "avf"; private final static String KEY_PROPERTIES = "as"; private final static String KEY_FILTERS = "hs"; private final static String KEY_BRACKETING = "bk"; private final static String KEY_ATTRIBUTE_VALUE_FILTERING_SHOULD_INCLUDE_MATCHES = "i"; - private final static String KEY_ATTRIBUTE_VALUE_FILTERING_ATTRIBUTE= "a"; - private final static String KEY_ATTRIBUTE_VALUE_FILTERING_VALUE= "v"; - private final static String KEY_EVENT_TYPES_FILTER = "et"; - private final static String KEY_EVENT_NAMES_FILTER = "ec"; - private final static String KEY_EVENT_ATTRIBUTES_FILTER = "ea"; - private final static String KEY_SCREEN_NAME_FILTER = "svec"; - private final static String KEY_SCREEN_ATTRIBUTES_FILTER = "svea"; - private final static String KEY_USER_IDENTITY_FILTER = "uid"; - private final static String KEY_USER_ATTRIBUTE_FILTER = "ua"; + final static String KEY_ATTRIBUTE_VALUE_FILTERING_ATTRIBUTE = "a"; + private final static String KEY_ATTRIBUTE_VALUE_FILTERING_VALUE = "v"; + final static String KEY_EVENT_TYPES_FILTER = "et"; + final static String KEY_EVENT_NAMES_FILTER = "ec"; + final static String KEY_EVENT_ATTRIBUTES_FILTER = "ea"; + final static String KEY_SCREEN_NAME_FILTER = "svec"; + final static String KEY_SCREEN_ATTRIBUTES_FILTER = "svea"; + final static String KEY_USER_IDENTITY_FILTER = "uid"; + final static String KEY_USER_ATTRIBUTE_FILTER = "ua"; private final static String KEY_EVENT_ATTRIBUTE_ADD_USER = "eaa"; private final static String KEY_EVENT_ATTRIBUTE_REMOVE_USER = "ear"; private final static String KEY_EVENT_ATTRIBUTE_SINGLE_ITEM_USER = "eas"; @@ -56,8 +56,8 @@ public class KitConfiguration { private final static String KEY_COMMERCE_ENTITY_FILTERS = "ent"; private final static String KEY_COMMERCE_ENTITY_ATTRIBUTE_FILTERS = "afa"; private final static String KEY_CONSENT_FORWARDING_RULES = "crvf"; - private final static String KEY_CONSENT_FORWARDING_RULES_SHOULD_INCLUDE_MATCHES = "i"; - private final static String KEY_CONSENT_FORWARDING_RULES_ARRAY = "v"; + final static String KEY_CONSENT_FORWARDING_RULES_SHOULD_INCLUDE_MATCHES = "i"; + final static String KEY_CONSENT_FORWARDING_RULES_ARRAY = "v"; private final static String KEY_CONSENT_FORWARDING_RULES_VALUE_CONSENTED = "c"; private final static String KEY_CONSENT_FORWARDING_RULES_VALUE_HASH = "h"; private final static String KEY_EXCLUDE_ANONYMOUS_USERS = "eau"; @@ -114,16 +114,354 @@ public SparseBooleanArray getScreenAttributeFilters() { return mScreenAttributeFilters; } - public static KitConfiguration createKitConfiguration(JSONObject json) throws JSONException{ + public static KitConfiguration createKitConfiguration(JSONObject json) throws JSONException { return new KitConfiguration().parseConfiguration(json); } + public KitConfiguration applySideloadedKits(MPSideloadedFilters sideloadedFilters) { + Map sideloadedFiltersMap = sideloadedFilters.getFilters(); + if (sideloadedFiltersMap.containsKey(KEY_ATTRIBUTE_VALUE_FILTERING)) { + JSONObject avfShouldIncludeMatchesJSONObject = sideloadedFiltersMap.get(KEY_ATTRIBUTE_VALUE_FILTERING); + if (avfShouldIncludeMatchesJSONObject.has(KEY_ATTRIBUTE_VALUE_FILTERING_SHOULD_INCLUDE_MATCHES)) { + try { + this.avfShouldIncludeMatches = avfShouldIncludeMatchesJSONObject.getBoolean(KEY_ATTRIBUTE_VALUE_FILTERING_SHOULD_INCLUDE_MATCHES); + } catch (JSONException e) { + } + } + if (avfShouldIncludeMatchesJSONObject.has(KEY_ATTRIBUTE_VALUE_FILTERING_ATTRIBUTE)) { + try { + this.avfHashedAttribute = avfShouldIncludeMatchesJSONObject.getInt(KEY_ATTRIBUTE_VALUE_FILTERING_ATTRIBUTE); + } catch (JSONException e) { + } + } + if (avfShouldIncludeMatchesJSONObject.has(KEY_ATTRIBUTE_VALUE_FILTERING_VALUE)) { + try { + this.avfHashedValue = avfShouldIncludeMatchesJSONObject.getInt(KEY_ATTRIBUTE_VALUE_FILTERING_VALUE); + } catch (JSONException e) { + } + } + } + + if (sideloadedFiltersMap.containsKey(KEY_EVENT_TYPES_FILTER)) { + try { + JSONObject typeFiltersSparseArray = sideloadedFiltersMap.get(KEY_EVENT_TYPES_FILTER); + if (typeFiltersSparseArray != null && typeFiltersSparseArray.length() > 0) { + this.mTypeFilters = convertToSparseArray(typeFiltersSparseArray); + } + } catch (Exception e) { + } + } + + if (sideloadedFiltersMap.containsKey(KEY_EVENT_NAMES_FILTER)) { + try { + JSONObject nameFiltersSparseArray = sideloadedFiltersMap.get(KEY_EVENT_NAMES_FILTER); + if (nameFiltersSparseArray != null && nameFiltersSparseArray.length() > 0) { + this.mNameFilters = convertToSparseArray(nameFiltersSparseArray); + } + } catch (Exception e) { + } + } + + if (sideloadedFiltersMap.containsKey(KEY_EVENT_ATTRIBUTES_FILTER)) { + try { + JSONObject attrFiltersSparseArray = sideloadedFiltersMap.get(KEY_EVENT_ATTRIBUTES_FILTER); + if (attrFiltersSparseArray != null && attrFiltersSparseArray.length() > 0) { + this.mAttributeFilters = convertToSparseArray(attrFiltersSparseArray); + } + } catch (Exception e) { + } + } + + if (sideloadedFiltersMap.containsKey(KEY_SCREEN_NAME_FILTER)) { + try { + JSONObject screenNameFilterSparseArray = sideloadedFiltersMap.get(KEY_SCREEN_NAME_FILTER); + if (screenNameFilterSparseArray != null && screenNameFilterSparseArray.length() > 0) { + this.mScreenNameFilters = convertToSparseArray(screenNameFilterSparseArray); + } + } catch (Exception e) { + } + } + + if (sideloadedFiltersMap.containsKey(KEY_SCREEN_ATTRIBUTES_FILTER)) { + try { + JSONObject screenAttrFilterSparseArray = sideloadedFiltersMap.get(KEY_SCREEN_ATTRIBUTES_FILTER); + if (screenAttrFilterSparseArray != null && screenAttrFilterSparseArray.length() > 0) { + this.mScreenAttributeFilters = convertToSparseArray(screenAttrFilterSparseArray); + } + } catch (Exception e) { + } + } + + if (sideloadedFiltersMap.containsKey(KEY_USER_IDENTITY_FILTER)) { + try { + JSONObject userIdentityFilterSparseArray = sideloadedFiltersMap.get(KEY_USER_IDENTITY_FILTER); + if (userIdentityFilterSparseArray != null && userIdentityFilterSparseArray.length() > 0) { + this.mUserIdentityFilters = convertToSparseArray(userIdentityFilterSparseArray); + } + } catch (Exception e) { + } + } + + if (sideloadedFiltersMap.containsKey(KEY_COMMERCE_ATTRIBUTE_FILTER)) { + try { + JSONObject commerceAttrFilterSparseArray = sideloadedFiltersMap.get(KEY_COMMERCE_ATTRIBUTE_FILTER); + if (commerceAttrFilterSparseArray != null && commerceAttrFilterSparseArray.length() > 0) { + this.mCommerceAttributeFilters = convertToSparseArray(commerceAttrFilterSparseArray); + } + } catch (Exception e) { + } + } + + if (sideloadedFiltersMap.containsKey(KEY_COMMERCE_ENTITY_FILTERS)) { + try { + JSONObject commerceEntityFilterSparseArray = sideloadedFiltersMap.get(KEY_COMMERCE_ENTITY_FILTERS); + if (commerceEntityFilterSparseArray != null && commerceEntityFilterSparseArray.length() > 0) { + this.mCommerceAttributeFilters = convertToSparseArray(commerceEntityFilterSparseArray); + } + } catch (Exception e) { + } + } + + if (sideloadedFiltersMap.containsKey(KEY_EVENT_ATTRIBUTE_ADD_USER)) { + try { + JSONObject attrAddToUserMap = sideloadedFiltersMap.get(KEY_EVENT_ATTRIBUTE_ADD_USER); + if (attrAddToUserMap != null && attrAddToUserMap.length() > 0) { + this.mAttributeAddToUser = convertToSparseMap(attrAddToUserMap); + } + } catch (Exception e) { + } + } + + if (sideloadedFiltersMap.containsKey(KEY_EVENT_ATTRIBUTE_REMOVE_USER)) { + try { + JSONObject attrRemovalFromUserMap = sideloadedFiltersMap.get(KEY_EVENT_ATTRIBUTE_REMOVE_USER); + if (attrRemovalFromUserMap != null && attrRemovalFromUserMap.length() > 0) { + this.mAttributeRemoveFromUser = convertToSparseMap(attrRemovalFromUserMap); + } + } catch (Exception e) { + } + } + + if (sideloadedFiltersMap.containsKey(KEY_EVENT_ATTRIBUTE_SINGLE_ITEM_USER)) { + try { + JSONObject attrSingleItemUserMap = sideloadedFiltersMap.get(KEY_EVENT_ATTRIBUTE_SINGLE_ITEM_USER); + if (attrSingleItemUserMap != null && attrSingleItemUserMap.length() > 0) { + this.mAttributeSingleItemUser = convertToSparseMap(attrSingleItemUserMap); + } + } catch (Exception e) { + } + } + + return this; + } + + public SparseBooleanArray getValueFromSideloadedFilterToSparseArray(Map sideloadedFiltersMap, String key) { + SparseBooleanArray result = null; + JSONObject value = sideloadedFiltersMap.get(key); + if (value != null && value.length() > 0) { + result = convertToSparseArray(value); + } + return result; + } + + public Map getValueFromSideloadedFilterToMap(Map sideloadedFiltersMap, String key) { + Map result = null; + JSONObject value = sideloadedFiltersMap.get(key); + if (value != null && value.length() > 0) { + result = convertToSparseMap(value); + } + return result; + } + + private JSONObject getKeyFilters() throws JSONException { + JSONObject keyFilters = new JSONObject(); + JSONObject typeFilters = convertSparseArrayToJsonObject(this.mTypeFilters); + if (typeFilters != null) { + keyFilters.put(KEY_EVENT_TYPES_FILTER, typeFilters); + } + + JSONObject mNameFilters = convertSparseArrayToJsonObject(this.mNameFilters); + if (mNameFilters != null) { + keyFilters.put(KEY_EVENT_NAMES_FILTER, mNameFilters); + } + + JSONObject mAttributeFilters = convertSparseArrayToJsonObject(this.mAttributeFilters); + if (mAttributeFilters != null) { + keyFilters.put(KEY_EVENT_ATTRIBUTES_FILTER, mAttributeFilters); + } + + JSONObject mScreenNameFilters = convertSparseArrayToJsonObject(this.mScreenNameFilters); + if (mScreenNameFilters != null) { + keyFilters.put(KEY_SCREEN_NAME_FILTER, mScreenNameFilters); + } + + JSONObject mScreenAttributeFilters = convertSparseArrayToJsonObject(this.mScreenAttributeFilters); + if (mScreenAttributeFilters != null) { + keyFilters.put(KEY_SCREEN_ATTRIBUTES_FILTER, mScreenAttributeFilters); + } + + JSONObject mUserIdentityFilters = convertSparseArrayToJsonObject(this.mUserIdentityFilters); + if (mUserIdentityFilters != null) { + keyFilters.put(KEY_USER_IDENTITY_FILTER, mUserIdentityFilters); + } + + JSONObject mCommerceAttributeFilters = convertSparseArrayToJsonObject(this.mCommerceAttributeFilters); + if (mCommerceAttributeFilters != null) { + keyFilters.put(KEY_COMMERCE_ATTRIBUTE_FILTER, mCommerceAttributeFilters); + } + + JSONObject mCommerceEntityFilters = convertSparseArrayToJsonObject(this.mCommerceEntityFilters); + if (mCommerceEntityFilters != null) { + keyFilters.put(KEY_COMMERCE_ENTITY_FILTERS, mCommerceEntityFilters); + } + + JSONObject mAttributeAddToUser = convertSparseMapToJsonObject(this.mAttributeAddToUser); + if (mAttributeAddToUser != null) { + keyFilters.put(KEY_EVENT_ATTRIBUTE_ADD_USER, mAttributeAddToUser); + } + + JSONObject mAttributeRemoveFromUser = convertSparseMapToJsonObject(this.mAttributeRemoveFromUser); + if (mAttributeRemoveFromUser != null) { + keyFilters.put(KEY_EVENT_ATTRIBUTE_REMOVE_USER, mAttributeRemoveFromUser); + } + + JSONObject mAttributeSingleItemUser = convertSparseMapToJsonObject(this.mAttributeSingleItemUser); + if (mAttributeSingleItemUser != null) { + keyFilters.put(KEY_EVENT_ATTRIBUTE_SINGLE_ITEM_USER, mAttributeSingleItemUser); + } + if (this.mCommerceEntityAttributeFilters != null && !this.mCommerceEntityAttributeFilters.isEmpty()) { + JSONObject entityFilter = new JSONObject(); + for (Map.Entry entity : this.mCommerceEntityAttributeFilters.entrySet()) { + JSONObject convertedObj = convertSparseArrayToJsonObject(entity.getValue()); + entityFilter.put(Integer.toString(entity.getKey()), convertedObj); + } + keyFilters.put(KEY_COMMERCE_ENTITY_ATTRIBUTE_FILTERS, entityFilter); + } + return keyFilters; + } + + private JSONObject getBracketing() throws JSONException { + JSONObject bracketing = new JSONObject(); + if (this.lowBracket != 0) { + bracketing.put(KEY_BRACKETING_LOW, this.lowBracket); + } + if (this.highBracket != 101) { + bracketing.put(KEY_BRACKETING_HIGH, this.highBracket); + } + return bracketing; + } + + private JSONArray getCustomMapping() { + JSONArray array = new JSONArray(); + for (CustomMapping mapping : this.customMappingList) { + JSONObject rawJson = mapping.rawJsonProjection; + if (rawJson != null) { + array.put(rawJson); + } + } + return array; + } + + private JSONObject getConsentRules() throws JSONException { + JSONObject consent = new JSONObject(); + consent.put(KEY_CONSENT_FORWARDING_RULES_SHOULD_INCLUDE_MATCHES, this.consentForwardingIncludeMatches); + JSONArray consentArray = new JSONArray(); + for (Map.Entry entry : this.mConsentForwardingRules.entrySet()) { + JSONObject consentObj = new JSONObject(); + consentObj.put(KEY_CONSENT_FORWARDING_RULES_VALUE_HASH, entry.getKey()); + consentObj.put(KEY_CONSENT_FORWARDING_RULES_VALUE_CONSENTED, entry.getValue()); + consentArray.put(consentObj); + } + consent.put(KEY_CONSENT_FORWARDING_RULES_ARRAY, consentArray); + return consent; + } + + private JSONObject getAttributeValueFiltering() throws JSONException { + JSONObject filtering = new JSONObject(); + filtering.put(KEY_ATTRIBUTE_VALUE_FILTERING_SHOULD_INCLUDE_MATCHES, this.avfShouldIncludeMatches); + filtering.put(KEY_ATTRIBUTE_VALUE_FILTERING_ATTRIBUTE, this.avfHashedAttribute); + filtering.put(KEY_ATTRIBUTE_VALUE_FILTERING_VALUE, this.avfHashedValue); + return filtering; + } + + public JSONObject convertToJsonObject() { + JSONObject object = new JSONObject(); + try { + object.put(KEY_ID, this.kitId); + + if (this.avfIsActive) { + JSONObject filtering = getAttributeValueFiltering(); + object.put(KEY_ATTRIBUTE_VALUE_FILTERING, filtering); + } + + if (!this.settings.isEmpty()) { + object.put(KEY_PROPERTIES, this.settings.toString()); + } + + JSONObject keyFilters = getKeyFilters(); + if (keyFilters.length() > 0) { + object.put(KEY_FILTERS, keyFilters); + } + + JSONObject bracketing = getBracketing(); + if (bracketing.length() > 0) { + object.put(KEY_BRACKETING, bracketing); + } + + JSONArray customMapping = getCustomMapping(); + if (customMapping.length() > 0) { + object.put(KEY_PROJECTIONS, customMapping); + } + + if (this.mConsentForwardingRules != null && !this.mConsentForwardingRules.isEmpty()) { + JSONObject consent = getConsentRules(); + object.put(KEY_CONSENT_FORWARDING_RULES, consent); + } + + object.put(KEY_EXCLUDE_ANONYMOUS_USERS, this.mExcludeAnonymousUsers); + } catch (JSONException jse) { + Logger.error("Issue while converting KitConfigurationToJsonObject: "); + } + return object; + } + + private static JSONObject convertSparseMapToJsonObject(Map map) throws JSONException { + if (map == null || map.size() == 0) { + return null; + } + JSONObject obj = new JSONObject(); + for (Map.Entry entry : map.entrySet()) { + try { + obj.put(Integer.toString(entry.getKey()), entry.getValue()); + } catch (JSONException jse) { + Logger.error("Issue while parsing kit configuration: " + jse.getMessage()); + } + } + return obj; + } + + public static JSONObject convertSparseArrayToJsonObject(SparseBooleanArray array) throws JSONException { + if (array == null || array.size() == 0) { + return null; + } + JSONObject object = new JSONObject(); + for (int i = 0; i < array.size(); i++) { + int key = array.keyAt(i); + try { + object.put(Integer.toString(key), array.get(key)); + } catch (JSONException jse) { + Logger.error("Issue while parsing kit configuration: " + jse.getMessage()); + } + } + return object; + } + public KitConfiguration() { super(); } public KitConfiguration parseConfiguration(JSONObject json) throws JSONException { - kitId = json.getInt(KEY_ID); + kitId = json.optInt(KEY_ID); if (json.has(KEY_ATTRIBUTE_VALUE_FILTERING)) { avfIsActive = true; try { @@ -329,7 +667,7 @@ public boolean isConsentStateFilterMatch(ConsentState consentState) { if (consented != null && consented == ccpaConsent.isConsented()) { return true; } - } + } return false; } @@ -489,7 +827,7 @@ private CommerceEvent filterCommerceEntities(CommerceEvent filteredEvent) { && attributes.size() > 0) { Map newAttributes = new HashMap(attributes.size()); for (Map.Entry attribute : attributes.entrySet()) { - if ( shouldForwardAttribute(attributeFilters, attribute.getKey())) { + if (shouldForwardAttribute(attributeFilters, attribute.getKey())) { newAttributes.put(attribute.getKey(), attribute.getValue()); } } @@ -645,7 +983,7 @@ public boolean shouldHonorOptOut() { boolean shouldSetIdentity(MParticle.IdentityType identityType) { SparseBooleanArray userIdentityFilters = getUserIdentityFilters(); - return userIdentityFilters == null || + return userIdentityFilters == null || userIdentityFilters.size() == 0 || userIdentityFilters.get(identityType.getValue(), true); } 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 4f4baf9a5..89a18f16a 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 @@ -7,6 +7,7 @@ import android.location.Location; import android.net.Uri; import android.os.Bundle; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -16,7 +17,10 @@ import com.mparticle.commerce.CommerceEvent; import com.mparticle.consent.ConsentState; import com.mparticle.identity.MParticleUser; +import com.mparticle.internal.SideloadedKit; +import org.jetbrains.annotations.NotNull; +import org.json.JSONException; import org.json.JSONObject; import java.lang.ref.WeakReference; @@ -76,6 +80,13 @@ public KitConfiguration getConfiguration() { return mKitConfiguration; } + public JSONObject getJsonConfig() { + if(mKitConfiguration!=null){ + return mKitConfiguration.convertToJsonObject(); + } + return null; + } + public final KitIntegration setConfiguration(KitConfiguration configuration) { mKitConfiguration = configuration; return this; @@ -174,7 +185,7 @@ public final MParticleUser getCurrentUser() { public final MParticleUser getUser(Long mpid) { MParticle instance = MParticle.getInstance(); if (instance != null) { - MParticleUser user =instance.Identity().getUser(mpid); + MParticleUser user = instance.Identity().getUser(mpid); return FilteredMParticleUser.getInstance(user, this); } return FilteredMParticleUser.getInstance(null, this); @@ -195,7 +206,6 @@ public final MParticleUser getUser(Long mpid) { * * @param settings the settings that have been configured in mParticle UI. Use this to extract your API key, etc * @param context an Application Context object - * * @throws IllegalArgumentException if the kit is unable to start based on the provided settings. */ protected abstract List onKitCreate(Map settings, Context context) throws IllegalArgumentException; @@ -217,7 +227,7 @@ final void onKitCleanup() { if (allValues != null && allValues.size() > 0) { getKitPreferences().edit().clear().apply(); } - }catch (NullPointerException npe) { + } catch (NullPointerException npe) { } } @@ -371,13 +381,12 @@ public interface ActivityListener { /** * Kit should implement this interface to listen for mParticle session-start and session-end messages. - * */ public interface SessionListener { /** * mParticle will start a new session when: - * 1. The app is brought to the foreground, and there isn't already an active session. - * 2. Any event (ie custom events, screen events, user attributes, etc) is logged by the hosting app + * 1. The app is brought to the foreground, and there isn't already an active session. + * 2. Any event (ie custom events, screen events, user attributes, etc) is logged by the hosting app * * @return */ @@ -404,7 +413,7 @@ public interface AttributeListener { /** * Indicate to the mParticle Kit framework if this AttributeListener supports attribute-values as lists. - * + * * If an AttributeListener returns false, the setUserAttributeList method will never be called. Instead, setUserAttribute * will be called with the attribute-value lists combined as a csv. * @@ -546,7 +555,7 @@ public interface PushListener { * sync tokens as they're updated. * * @param instanceId the device instance ID registered with the FCM scope - * @param senderId the senderid with permissions for this instanceId + * @param senderId the senderid with permissions for this instanceId * @return true if the push registration was processed. */ boolean onPushRegistration(String instanceId, String senderId); @@ -582,7 +591,7 @@ public interface IdentityListener { public interface UserAttributeListener { - void onIncrementUserAttribute (String key, Number 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/KitIntegrationFactory.java b/android-kit-base/src/main/java/com/mparticle/kits/KitIntegrationFactory.java index 47346bd9b..93dd7fcec 100644 --- a/android-kit-base/src/main/java/com/mparticle/kits/KitIntegrationFactory.java +++ b/android-kit-base/src/main/java/com/mparticle/kits/KitIntegrationFactory.java @@ -1,9 +1,9 @@ package com.mparticle.kits; -import androidx.annotation.NonNull; - import com.mparticle.MParticle; +import com.mparticle.MParticleOptions; import com.mparticle.internal.Logger; +import com.mparticle.internal.SideloadedKit; import org.json.JSONException; @@ -15,70 +15,79 @@ public class KitIntegrationFactory { final Map supportedKits = new HashMap<>(); + private Map knownIntegrations = new HashMap(); + private Map sideloadedKitMap = new HashMap<>(); - public KitIntegrationFactory() { - loadIntegrations(); + public KitIntegrationFactory(MParticleOptions options) { + supportedKits.clear(); + knownIntegrations.clear(); + setupKnownIntegrations(); + loadIntegrations(options); } /** * This is the canonical method mapping all known Kit/Module IDs to Kit class names. - * - * @return a mapping of module Ids to kit classes + * Mapping of module Ids to kit classes */ - protected Map getKnownIntegrations() { - Map kits = new HashMap(); - kits.put(MParticle.ServiceProviders.ADJUST, "com.mparticle.kits.AdjustKit"); - kits.put(MParticle.ServiceProviders.APPBOY, "com.mparticle.kits.AppboyKit"); - kits.put(MParticle.ServiceProviders.BRANCH_METRICS, "com.mparticle.kits.BranchMetricsKit"); - kits.put(MParticle.ServiceProviders.COMSCORE, "com.mparticle.kits.ComscoreKit"); - kits.put(MParticle.ServiceProviders.KOCHAVA, "com.mparticle.kits.KochavaKit"); - kits.put(MParticle.ServiceProviders.FORESEE_ID, "com.mparticle.kits.ForeseeKit"); - kits.put(MParticle.ServiceProviders.LOCALYTICS, "com.mparticle.kits.LocalyticsKit"); - kits.put(MParticle.ServiceProviders.FLURRY, "com.mparticle.kits.FlurryKit"); - kits.put(MParticle.ServiceProviders.WOOTRIC, "com.mparticle.kits.WootricKit"); - kits.put(MParticle.ServiceProviders.CRITTERCISM, "com.mparticle.kits.CrittercismKit"); - kits.put(MParticle.ServiceProviders.TUNE, "com.mparticle.kits.TuneKit"); - kits.put(MParticle.ServiceProviders.APPSFLYER, "com.mparticle.kits.AppsFlyerKit"); - kits.put(MParticle.ServiceProviders.APPTENTIVE, "com.mparticle.kits.ApptentiveKit"); - kits.put(MParticle.ServiceProviders.BUTTON, "com.mparticle.kits.ButtonKit"); - kits.put(MParticle.ServiceProviders.URBAN_AIRSHIP, "com.mparticle.kits.UrbanAirshipKit"); - kits.put(MParticle.ServiceProviders.LEANPLUM, "com.mparticle.kits.LeanplumKit"); - kits.put(MParticle.ServiceProviders.APPTIMIZE, "com.mparticle.kits.ApptimizeKit"); - kits.put(MParticle.ServiceProviders.REVEAL_MOBILE, "com.mparticle.kits.RevealMobileKit"); - kits.put(MParticle.ServiceProviders.RADAR, "com.mparticle.kits.RadarKit"); - kits.put(MParticle.ServiceProviders.ITERABLE, "com.mparticle.kits.IterableKit"); - kits.put(MParticle.ServiceProviders.SKYHOOK, "com.mparticle.kits.SkyhookKit"); - kits.put(MParticle.ServiceProviders.SINGULAR, "com.mparticle.kits.SingularKit"); - kits.put(MParticle.ServiceProviders.ADOBE, "com.mparticle.kits.AdobeKit"); - kits.put(MParticle.ServiceProviders.TAPLYTICS, "com.mparticle.kits.TaplyticsKit"); - kits.put(MParticle.ServiceProviders.OPTIMIZELY, "com.mparticle.kits.OptimizelyKit"); - kits.put(MParticle.ServiceProviders.RESPONSYS, "com.mparticle.kits.ResponsysKit"); - kits.put(MParticle.ServiceProviders.CLEVERTAP, "com.mparticle.kits.CleverTapKit"); - kits.put(MParticle.ServiceProviders.GOOGLE_ANALYTICS_FIREBASE, "com.mparticle.kits.GoogleAnalyticsFirebaseKit"); - kits.put(MParticle.ServiceProviders.GOOGLE_ANALYTICS_FIREBASE_GA4, "com.mparticle.kits.GoogleAnalyticsFirebaseGA4Kit"); - kits.put(MParticle.ServiceProviders.PILGRIM, "com.mparticle.kits.PilgrimKit"); - kits.put(MParticle.ServiceProviders.ONETRUST, "com.mparticle.kits.OneTrustKit"); - kits.put(MParticle.ServiceProviders.SWRVE, "com.mparticle.kits.SwrveKit"); - kits.put(MParticle.ServiceProviders.BLUESHIFT, "com.mparticle.kits.BlueshiftKit"); - kits.put(MParticle.ServiceProviders.NEURA, "com.mparticle.kits.NeuraKit"); - return kits; + private void setupKnownIntegrations() { + knownIntegrations.put(MParticle.ServiceProviders.ADJUST, "com.mparticle.kits.AdjustKit"); + knownIntegrations.put(MParticle.ServiceProviders.APPBOY, "com.mparticle.kits.AppboyKit"); + knownIntegrations.put(MParticle.ServiceProviders.BRANCH_METRICS, "com.mparticle.kits.BranchMetricsKit"); + knownIntegrations.put(MParticle.ServiceProviders.COMSCORE, "com.mparticle.kits.ComscoreKit"); + knownIntegrations.put(MParticle.ServiceProviders.KOCHAVA, "com.mparticle.kits.KochavaKit"); + knownIntegrations.put(MParticle.ServiceProviders.FORESEE_ID, "com.mparticle.kits.ForeseeKit"); + knownIntegrations.put(MParticle.ServiceProviders.LOCALYTICS, "com.mparticle.kits.LocalyticsKit"); + knownIntegrations.put(MParticle.ServiceProviders.FLURRY, "com.mparticle.kits.FlurryKit"); + knownIntegrations.put(MParticle.ServiceProviders.WOOTRIC, "com.mparticle.kits.WootricKit"); + knownIntegrations.put(MParticle.ServiceProviders.CRITTERCISM, "com.mparticle.kits.CrittercismKit"); + knownIntegrations.put(MParticle.ServiceProviders.TUNE, "com.mparticle.kits.TuneKit"); + knownIntegrations.put(MParticle.ServiceProviders.APPSFLYER, "com.mparticle.kits.AppsFlyerKit"); + knownIntegrations.put(MParticle.ServiceProviders.APPTENTIVE, "com.mparticle.kits.ApptentiveKit"); + knownIntegrations.put(MParticle.ServiceProviders.BUTTON, "com.mparticle.kits.ButtonKit"); + knownIntegrations.put(MParticle.ServiceProviders.URBAN_AIRSHIP, "com.mparticle.kits.UrbanAirshipKit"); + knownIntegrations.put(MParticle.ServiceProviders.LEANPLUM, "com.mparticle.kits.LeanplumKit"); + knownIntegrations.put(MParticle.ServiceProviders.APPTIMIZE, "com.mparticle.kits.ApptimizeKit"); + knownIntegrations.put(MParticle.ServiceProviders.REVEAL_MOBILE, "com.mparticle.kits.RevealMobileKit"); + knownIntegrations.put(MParticle.ServiceProviders.RADAR, "com.mparticle.kits.RadarKit"); + knownIntegrations.put(MParticle.ServiceProviders.ITERABLE, "com.mparticle.kits.IterableKit"); + knownIntegrations.put(MParticle.ServiceProviders.SKYHOOK, "com.mparticle.kits.SkyhookKit"); + knownIntegrations.put(MParticle.ServiceProviders.SINGULAR, "com.mparticle.kits.SingularKit"); + knownIntegrations.put(MParticle.ServiceProviders.ADOBE, "com.mparticle.kits.AdobeKit"); + knownIntegrations.put(MParticle.ServiceProviders.TAPLYTICS, "com.mparticle.kits.TaplyticsKit"); + knownIntegrations.put(MParticle.ServiceProviders.OPTIMIZELY, "com.mparticle.kits.OptimizelyKit"); + knownIntegrations.put(MParticle.ServiceProviders.RESPONSYS, "com.mparticle.kits.ResponsysKit"); + knownIntegrations.put(MParticle.ServiceProviders.CLEVERTAP, "com.mparticle.kits.CleverTapKit"); + knownIntegrations.put(MParticle.ServiceProviders.GOOGLE_ANALYTICS_FIREBASE, "com.mparticle.kits.GoogleAnalyticsFirebaseKit"); + knownIntegrations.put(MParticle.ServiceProviders.GOOGLE_ANALYTICS_FIREBASE_GA4, "com.mparticle.kits.GoogleAnalyticsFirebaseGA4Kit"); + knownIntegrations.put(MParticle.ServiceProviders.PILGRIM, "com.mparticle.kits.PilgrimKit"); + knownIntegrations.put(MParticle.ServiceProviders.ONETRUST, "com.mparticle.kits.OneTrustKit"); + knownIntegrations.put(MParticle.ServiceProviders.SWRVE, "com.mparticle.kits.SwrveKit"); + knownIntegrations.put(MParticle.ServiceProviders.BLUESHIFT, "com.mparticle.kits.BlueshiftKit"); + knownIntegrations.put(MParticle.ServiceProviders.NEURA, "com.mparticle.kits.NeuraKit"); } - public KitIntegration createInstance(KitManagerImpl manager, KitConfiguration configuration) throws JSONException, ClassNotFoundException{ - KitIntegration kit = createInstance(manager, configuration.getKitId()); + public KitIntegration createInstance(KitManagerImpl manager, KitConfiguration configuration) throws JSONException, ClassNotFoundException { + KitIntegration kit; + if (configuration.getKitId() >= MPSideloadedKit.MIN_SIDELOADED_KIT) { + kit = sideloadedKitMap.get(configuration.getKitId()); + if (kit != null && kit.getKitManager() == null) { + kit.setKitManager(manager); + } + } else { + kit = createInstance(manager, configuration.getKitId()); + } if (kit != null) { kit.setConfiguration(configuration); } return kit; } - public KitIntegration createInstance(KitManagerImpl manager, int moduleId) throws JSONException, ClassNotFoundException{ + public KitIntegration createInstance(KitManagerImpl manager, int moduleId) throws JSONException, ClassNotFoundException { if (!supportedKits.isEmpty()) { try { Constructor constructor = supportedKits.get(moduleId).getConstructor(); constructor.setAccessible(true); - return constructor.newInstance() - .setKitManager(manager); + return constructor.newInstance().setKitManager(manager); } catch (Exception e) { Logger.debug(e, "Failed to create Kit with ID: " + moduleId); } @@ -86,8 +95,20 @@ public KitIntegration createInstance(KitManagerImpl manager, int moduleId) throw return null; } - private void loadIntegrations() { - Map knownIntegrations = getKnownIntegrations(); + private void loadSideloadedIntegrations(MParticleOptions options) { + sideloadedKitMap.clear(); + for (SideloadedKit entry : options.getSideloadedKits()) { + if (entry instanceof MPSideloadedKit && !supportedKits.containsKey(((MPSideloadedKit) entry).getConfiguration().getKitId())) { + int kitId = ((MPSideloadedKit) entry).getConfiguration().getKitId(); + supportedKits.put(kitId, entry.getClass()); + sideloadedKitMap.put(kitId, (MPSideloadedKit) entry); + Logger.debug(((MPSideloadedKit) entry).getName() + " detected with kit id " + kitId); + } + } + } + + private void loadIntegrations(MParticleOptions options) { + loadSideloadedIntegrations(options); for (Map.Entry entry : knownIntegrations.entrySet()) { Class kitClass = loadKit(entry.getValue()); if (kitClass != null) { @@ -124,6 +145,6 @@ public Set getSupportedKits() { } public boolean isSupported(int kitModuleId) { - return supportedKits.containsKey(kitModuleId); + return supportedKits.containsKey(kitModuleId) || kitModuleId >= MPSideloadedKit.MIN_SIDELOADED_KIT; } } 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 b2a58337c..1c14aa5d4 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 @@ -9,6 +9,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; +import android.util.Log; import androidx.annotation.MainThread; import androidx.annotation.NonNull; @@ -56,6 +57,7 @@ public class KitManagerImpl implements KitManager, AttributionListener, UserAttributeListener, IdentityStateListener { private static HandlerThread kitHandlerThread; + static { kitHandlerThread = new HandlerThread("mParticle_kit_thread"); kitHandlerThread.start(); @@ -84,7 +86,7 @@ public KitManagerImpl(Context context, ReportingManager reportingManager, CoreCa mContext = context; mReportingManager = reportingManager; mCoreCallbacks = coreCallbacks; - mKitIntegrationFactory = new KitIntegrationFactory(); + mKitIntegrationFactory = new KitIntegrationFactory(options); MParticle instance = MParticle.getInstance(); if (instance != null) { instance.Identity().addIdentityStateListener(this); @@ -204,7 +206,7 @@ protected synchronized void configureKits(@NonNull List kitCon HashMap previousKits = new HashMap<>(providers); if (kitConfigurations != null) { - for (KitConfiguration configuration: kitConfigurations) { + for (KitConfiguration configuration : kitConfigurations) { try { int currentModuleID = configuration.getKitId(); if (configuration.shouldExcludeUser(user)) { @@ -212,15 +214,23 @@ protected synchronized void configureKits(@NonNull List kitCon continue; } if (!mKitIntegrationFactory.isSupported(configuration.getKitId())) { - Logger.debug("Kit id configured but is not bundled: " + currentModuleID); continue; } KitIntegration activeKit = providers.get(currentModuleID); if (activeKit == null) { - activeKit = mKitIntegrationFactory.createInstance(KitManagerImpl.this, configuration); - if (activeKit.isDisabled() || - !configuration.shouldIncludeFromConsentRules(user)) { - Logger.debug("Kit id configured but is filtered or disabled: " + currentModuleID); + boolean activeKitInstanceCreated = false; + try { + if (getSupportedKits().contains(configuration.getKitId())) { + activeKit = mKitIntegrationFactory.createInstance(KitManagerImpl.this, configuration); + activeKitInstanceCreated = activeKit != null; + } + } catch (Exception npe) { + } + if (!activeKitInstanceCreated && configuration.getKitId() >= MPSideloadedKit.MIN_SIDELOADED_KIT) { + Logger.verbose("De-initializing sideloaded kit with id: " + configuration.getKitId()); + continue; + } + if (!activeKitInstanceCreated || activeKit.isDisabled() || !configuration.shouldIncludeFromConsentRules(user)) { continue; } activeIds.add(currentModuleID); @@ -314,13 +324,13 @@ private void initializeKit(KitIntegration activeKit) { @Override public Map getKitStatus() { Map kitStatusMap = new HashMap<>(); - for(Integer kitId: mKitIntegrationFactory.getSupportedKits()) { + for (Integer kitId : mKitIntegrationFactory.getSupportedKits()) { kitStatusMap.put(kitId, KitStatus.NOT_CONFIGURED); } - for(KitConfiguration kitConfiguration: kitConfigurations) { + for (KitConfiguration kitConfiguration : kitConfigurations) { kitStatusMap.put(kitConfiguration.getKitId(), KitStatus.STOPPED); } - for(Map.Entry activeKit: providers.entrySet()) { + for (Map.Entry activeKit : providers.entrySet()) { if (!activeKit.getValue().isDisabled()) { kitStatusMap.put(activeKit.getKey(), KitStatus.ACTIVE); } @@ -415,7 +425,7 @@ public void logEvent(BaseEvent event) { return; } } - for (KitIntegration provider: providers.values()) { + for (KitIntegration provider : providers.values()) { try { List messages = provider.logBaseEvent(event); mCoreCallbacks.getKitListener().onKitApiCalled(provider.getConfiguration().getKitId(), !MPUtility.isEmpty(messages), event); @@ -494,7 +504,7 @@ protected void logCommerceEvent(CommerceEvent event) { List events = CommerceEventUtils.expand(filteredEvent); boolean forwarded = false; if (events != null) { - for (MPEvent expandedEvent: events) { + for (MPEvent expandedEvent : events) { List reporting = ((KitIntegration.EventListener) provider).logEvent(expandedEvent); mCoreCallbacks.getKitListener().onKitApiCalled("logMPEvent()", provider.getConfiguration().getKitId(), !MPUtility.isEmpty(reporting), expandedEvent); forwarded = forwarded || (reporting != null && reporting.size() > 0); @@ -549,7 +559,7 @@ public boolean onPushRegistration(String token, String senderId) { try { if (!provider.isDisabled()) { boolean onPushRegistration = ((KitIntegration.PushListener) provider).onPushRegistration(token, senderId); - mCoreCallbacks.getKitListener().onKitApiCalled(provider.getConfiguration().getKitId(),onPushRegistration, token, senderId); + mCoreCallbacks.getKitListener().onKitApiCalled(provider.getConfiguration().getKitId(), onPushRegistration, token, senderId); if (onPushRegistration) { ReportingMessage message = ReportingMessage.fromPushRegistrationMessage(provider); getReportingManager().log(message); @@ -719,9 +729,9 @@ public void incrementUserAttribute(String key, Number incrementedBy, String newV for (KitIntegration provider : providers.values()) { try { if (!provider.isDisabled() && KitConfiguration.shouldForwardAttribute(provider.getConfiguration().getUserAttributeFilters(), key)) - if (provider instanceof KitIntegration.UserAttributeListener) { - ((KitIntegration.UserAttributeListener) provider).onIncrementUserAttribute(key, incrementedBy, newValue, FilteredMParticleUser.getInstance(mpid, provider)); - } + if (provider instanceof KitIntegration.UserAttributeListener) { + ((KitIntegration.UserAttributeListener) provider).onIncrementUserAttribute(key, incrementedBy, newValue, FilteredMParticleUser.getInstance(mpid, provider)); + } if (provider instanceof KitIntegration.AttributeListener) { ((KitIntegration.AttributeListener) provider).setUserAttribute(key, newValue); } @@ -878,18 +888,16 @@ protected void logMPEvent(MPEvent event) { @Override public void logBatch(String batch) { - for (KitIntegration provider: providers.values()) { + for (KitIntegration provider : providers.values()) { try { if (provider instanceof KitIntegration.BatchListener) { JSONObject jsonObject = new JSONObject(batch); - List reportingMessages = ((KitIntegration.BatchListener)provider).logBatch(jsonObject); + List reportingMessages = ((KitIntegration.BatchListener) provider).logBatch(jsonObject); getReportingManager().logAll(reportingMessages); } - } - catch (JSONException jse) { + } catch (JSONException jse) { Logger.error(jse, "Failed to call logBatch (unable to deserialize Batch) for kit" + provider.getName() + ": " + jse.getMessage()); - } - catch (Exception e) { + } catch (Exception e) { Logger.warning("Failed to call logBatch for kit: " + provider.getName() + ": " + e.getMessage()); } } @@ -979,7 +987,7 @@ public void logScreen(MPEvent screenEvent) { System.currentTimeMillis(), filteredEvent.getCustomAttributeStrings()); boolean forwarded = false; - for (CustomMapping.ProjectionResult projectedEvent: projectedEvents) { + for (CustomMapping.ProjectionResult projectedEvent : projectedEvents) { List report = ((KitIntegration.EventListener) provider).logEvent(projectedEvent.getMPEvent()); mCoreCallbacks.getKitListener().onKitApiCalled("logMPEvent()", provider.getConfiguration().getKitId(), !MPUtility.isEmpty(report), projectedEvent); if (report != null && report.size() > 0) { @@ -1378,7 +1386,9 @@ private void initializeKitIntegrationFactory() { } if (mKitIntegrationFactory.supportedKits != null) { for (Integer kitId : mKitIntegrationFactory.supportedKits.keySet()) { - mCoreCallbacks.getKitListener().kitFound(kitId); + if (mCoreCallbacks.getKitListener() != null) { + mCoreCallbacks.getKitListener().kitFound(kitId); + } } } } @@ -1414,7 +1424,7 @@ public void addKitsLoadedListener(KitsLoadedListener kitsLoadedListener) { } private void onKitsLoaded(Map kits, Map previousKits, List kitConfigs) { - for(KitsLoadedListener listener: kitsLoadedListeners) { + for (KitsLoadedListener listener : kitsLoadedListeners) { listener.onKitsLoaded(kits, previousKits, kitConfigs); } } diff --git a/android-kit-base/src/main/java/com/mparticle/kits/MPSideloadedFilters.kt b/android-kit-base/src/main/java/com/mparticle/kits/MPSideloadedFilters.kt new file mode 100644 index 000000000..7665d9567 --- /dev/null +++ b/android-kit-base/src/main/java/com/mparticle/kits/MPSideloadedFilters.kt @@ -0,0 +1,9 @@ +package com.mparticle.kits + +import org.json.JSONObject + +class MPSideloadedFilters { + + var filters: MutableMap = mutableMapOf() + private set +} diff --git a/android-kit-base/src/main/java/com/mparticle/kits/MPSideloadedKit.kt b/android-kit-base/src/main/java/com/mparticle/kits/MPSideloadedKit.kt new file mode 100644 index 000000000..7a74ca0ab --- /dev/null +++ b/android-kit-base/src/main/java/com/mparticle/kits/MPSideloadedKit.kt @@ -0,0 +1,43 @@ +package com.mparticle.kits + +import android.content.Context +import com.mparticle.internal.SideloadedKit +import org.json.JSONObject + +abstract class MPSideloadedKit(val kitId: Int) : KitIntegration(), SideloadedKit { + + companion object { + const val MIN_SIDELOADED_KIT = 1000000 + } + + init { + configuration = KitConfiguration.createKitConfiguration( + JSONObject().put( + KitConfiguration.KEY_ID, + kitId + ) + ) + } + + override fun kitId(): Int = kitId + + override fun getName(): String = this::class.java.name.split(".").last().orEmpty() + + override fun onKitCreate( + settings: MutableMap?, + context: Context? + ): MutableList = mutableListOf() + + override fun setOptOut(optedOut: Boolean): MutableList = mutableListOf() + + fun addFilters(filter: MPSideloadedFilters): MPSideloadedKit { + configuration = configuration.applyFilters(filter) + return this + } + + override fun getJsonConfig(): JSONObject? = super.getJsonConfig() + + private fun KitConfiguration.applyFilters(filters: MPSideloadedFilters): KitConfiguration? { + return configuration?.applySideloadedKits(filters) + } +} diff --git a/android-kit-base/src/main/java/com/mparticle/kits/mappings/CustomMapping.java b/android-kit-base/src/main/java/com/mparticle/kits/mappings/CustomMapping.java index 373e9f41d..9dcb73e3d 100644 --- a/android-kit-base/src/main/java/com/mparticle/kits/mappings/CustomMapping.java +++ b/android-kit-base/src/main/java/com/mparticle/kits/mappings/CustomMapping.java @@ -45,12 +45,14 @@ public class CustomMapping { public final static String PROPERTY_LOCATION_PRODUCT_FIELD = "ProductField"; public final static String PROPERTY_LOCATION_PRODUCT_ATTRIBUTE = "ProductAttribute"; public final static String PROPERTY_LOCATION_PROMOTION_FIELD = "PromotionField"; + public JSONObject rawJsonProjection; public List getMatchList() { return matchList; } public CustomMapping(JSONObject projectionJson) throws JSONException { + this.rawJsonProjection = projectionJson; mID = projectionJson.getInt("id"); mMappingId = projectionJson.optInt("pmid"); mModuleMappingId = projectionJson.optInt("pmmid"); @@ -103,9 +105,9 @@ public CustomMapping(JSONObject projectionJson) throws JSONException { public int compare(AttributeMap lhs, AttributeMap rhs) { if (lhs.mIsRequired == rhs.mIsRequired) { return 0; - }else if (lhs.mIsRequired && !rhs.mIsRequired) { + } else if (lhs.mIsRequired && !rhs.mIsRequired) { return -1; - }else { + } else { return 1; } } @@ -164,6 +166,7 @@ private ProjectionResult projectMPEvent(MPEvent event) { builder.customAttributes(newAttributes); return new ProjectionResult(builder.build(), mID); } + public List project(EventWrapper.CommerceEventWrapper commerceEventWrapper) { List projectionResults = new LinkedList(); CommerceEvent commerceEvent = commerceEventWrapper.getEvent(); @@ -171,29 +174,26 @@ public List project(EventWrapper.CommerceEventWrapper commerce //TODO Impression projections are not supported for now if (eventType == CommerceEventUtils.Constants.EVENT_TYPE_IMPRESSION) { return null; - }else if (eventType == CommerceEventUtils.Constants.EVENT_TYPE_PROMOTION_CLICK || eventType == CommerceEventUtils.Constants.EVENT_TYPE_PROMOTION_VIEW) { + } else if (eventType == CommerceEventUtils.Constants.EVENT_TYPE_PROMOTION_CLICK || eventType == CommerceEventUtils.Constants.EVENT_TYPE_PROMOTION_VIEW) { List promotions = commerceEvent.getPromotions(); - if (promotions == null || promotions.size() == 0){ + if (promotions == null || promotions.size() == 0) { ProjectionResult projectionResult = projectCommerceEvent(commerceEventWrapper, null, null); if (projectionResult != null) { projectionResults.add(projectionResult); } - }else{ + } else { if (isSelectorLast) { Promotion promotion = promotions.get(promotions.size() - 1); ProjectionResult projectionResult = projectCommerceEvent(commerceEventWrapper, null, promotion); if (projectionResult != null) { projectionResults.add(projectionResult); } - }else{ + } else { for (int i = 0; i < promotions.size(); i++) { ProjectionResult projectionResult = projectCommerceEvent(commerceEventWrapper, null, promotions.get(i)); if (projectionResult != null) { if (projectionResult.getCommerceEvent() != null) { - CommerceEvent foreachCommerceEvent = new CommerceEvent.Builder(projectionResult.getCommerceEvent()) - .promotions(null) - .addPromotion(promotions.get(i)) - .build(); + CommerceEvent foreachCommerceEvent = new CommerceEvent.Builder(projectionResult.getCommerceEvent()).promotions(null).addPromotion(promotions.get(i)).build(); projectionResult.mCommerceEvent = foreachCommerceEvent; } projectionResults.add(projectionResult); @@ -202,27 +202,24 @@ public List project(EventWrapper.CommerceEventWrapper commerce } } - }else { + } else { List products = commerceEvent.getProducts(); - if (isSelectorLast){ + if (isSelectorLast) { Product product = null; - if (products != null && products.size() > 0){ + if (products != null && products.size() > 0) { product = products.get(products.size() - 1); } ProjectionResult projectionResult = projectCommerceEvent(commerceEventWrapper, product, null); if (projectionResult != null) { projectionResults.add(projectionResult); } - }else { + } else { if (products != null) { for (int i = 0; i < products.size(); i++) { ProjectionResult projectionResult = projectCommerceEvent(commerceEventWrapper, products.get(i), null); if (projectionResult != null) { if (projectionResult.getCommerceEvent() != null) { - CommerceEvent foreachCommerceEvent = new CommerceEvent.Builder(projectionResult.getCommerceEvent()) - .products(null) - .addProduct(products.get(i)) - .build(); + CommerceEvent foreachCommerceEvent = new CommerceEvent.Builder(projectionResult.getCommerceEvent()).products(null).addProduct(products.get(i)).build(); projectionResult.mCommerceEvent = foreachCommerceEvent; } @@ -236,10 +233,11 @@ public List project(EventWrapper.CommerceEventWrapper commerce if (projectionResults.size() > 0) { return projectionResults; - }else{ + } else { return null; } } + public List project(EventWrapper.MPEventWrapper event) { List projectionResults = new LinkedList(); @@ -250,7 +248,7 @@ public List project(EventWrapper.MPEventWrapper event) { if (projectionResults.size() > 0) { return projectionResults; - }else{ + } else { return null; } } @@ -266,12 +264,12 @@ private boolean mapAttributes(List projectionList, EventWrapper ev entry = eventWrapper.findAttribute(attProjection.mLocation, Integer.parseInt(attProjection.mValue), product, promotion); } else if (attProjection.mMatchType.startsWith(MATCH_TYPE_FIELD) && eventWrapper.getEvent() instanceof MPEvent) { //match_type field is a special case for mapping the event name to an attribute, only supported by MPEvent - entry = new AbstractMap.SimpleEntry(attProjection.mProjectedAttributeName, ((MPEvent)eventWrapper.getEvent()).getEventName()); + entry = new AbstractMap.SimpleEntry(attProjection.mProjectedAttributeName, ((MPEvent) eventWrapper.getEvent()).getEventName()); } if (entry == null || !attProjection.matchesDataType(entry.getValue())) { if (attProjection.mIsRequired) { return false; - }else { + } else { continue; } } @@ -317,21 +315,10 @@ private ProjectionResult projectCommerceEvent(EventWrapper.CommerceEventWrapper } } } - if (mOutboundMessageType == 16){ - return new ProjectionResult( - new CommerceEvent.Builder(eventWrapper.getEvent()) - .internalEventName(mProjectedEventName) - .customAttributes(mappedAttributes) - .build(), - mID - ); - }else { - return new ProjectionResult( - new MPEvent.Builder(mProjectedEventName, MParticle.EventType.Transaction) - .customAttributes(mappedAttributes) - .build(), - mID - ); + if (mOutboundMessageType == 16) { + return new ProjectionResult(new CommerceEvent.Builder(eventWrapper.getEvent()).internalEventName(mProjectedEventName).customAttributes(mappedAttributes).build(), mID); + } else { + return new ProjectionResult(new MPEvent.Builder(mProjectedEventName, MParticle.EventType.Transaction).customAttributes(mappedAttributes).build(), mID); } } @@ -368,11 +355,7 @@ public boolean equals(Object o) { @Override public String toString() { - return "projected_attribute_name: " + mProjectedAttributeName + "\n" + - "match_type: " + mMatchType + "\n" + - "value: " + mValue + "\n" + - "data_type: " + mDataType + "\n" + - "is_required: " + mIsRequired; + return "projected_attribute_name: " + mProjectedAttributeName + "\n" + "match_type: " + mMatchType + "\n" + "value: " + mValue + "\n" + "data_type: " + mDataType + "\n" + "is_required: " + mIsRequired; } public boolean matchesDataType(String value) { diff --git a/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt b/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt index 6b9d8070a..c6c077386 100644 --- a/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt +++ b/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerImplTest.kt @@ -3,6 +3,7 @@ package com.mparticle.kits import com.mparticle.BaseEvent import com.mparticle.MPEvent import com.mparticle.MParticle +import com.mparticle.MParticleOptions import com.mparticle.commerce.CommerceEvent import com.mparticle.commerce.Product import com.mparticle.consent.ConsentState @@ -10,7 +11,9 @@ import com.mparticle.consent.GDPRConsent import com.mparticle.identity.IdentityApi import com.mparticle.identity.MParticleUser import com.mparticle.internal.CoreCallbacks +import com.mparticle.internal.SideloadedKit import com.mparticle.kits.KitIntegration.AttributeListener +import com.mparticle.mock.MockContext import com.mparticle.mock.MockKitConfiguration import com.mparticle.mock.MockKitManagerImpl import com.mparticle.mock.MockMParticle @@ -48,6 +51,12 @@ class KitManagerImplTest { Assert.assertEquals(factory, manager.mKitIntegrationFactory) } + private fun createKitsMap(ids: List, type: Class<*> = KitIntegration::class.java): HashMap> { + val map = hashMapOf>() + ids.forEach { map.put(it, type) } + return map + } + @Test @Throws(Exception::class) fun testShouldEnableKit() { @@ -63,6 +72,8 @@ class KitManagerImplTest { KitIntegrationFactory::class.java ) manager.setKitFactory(factory) + + Mockito.`when`(factory.getSupportedKits()).thenReturn(createKitsMap(listOf(1, 2)).keys) Mockito.`when`(factory.isSupported(Mockito.anyInt())).thenReturn(true) val mockKit = Mockito.mock(KitIntegration::class.java) Mockito.`when`(mockKit.configuration).thenReturn( @@ -101,6 +112,7 @@ class KitManagerImplTest { KitIntegrationFactory::class.java ) manager.setKitFactory(factory) + Mockito.`when`(factory.getSupportedKits()).thenReturn(createKitsMap(listOf(1, 2, 3)).keys) Mockito.`when`(factory.isSupported(Mockito.anyInt())).thenReturn(true) val mockKit = Mockito.mock(KitIntegration::class.java) Mockito.`when`(mockKit.configuration).thenReturn( @@ -137,6 +149,7 @@ class KitManagerImplTest { KitIntegrationFactory::class.java ) manager.setKitFactory(factory) + Mockito.`when`(factory.getSupportedKits()).thenReturn(createKitsMap(listOf(1, 2)).keys) Mockito.`when`(factory.isSupported(Mockito.anyInt())).thenReturn(true) val mockKit = Mockito.mock(KitIntegration::class.java) Mockito.`when`(mockKit.configuration).thenReturn( @@ -173,6 +186,7 @@ class KitManagerImplTest { KitIntegrationFactory::class.java ) manager.setKitFactory(factory) + Mockito.`when`(factory.getSupportedKits()).thenReturn(createKitsMap(listOf(1, 2)).keys) Mockito.`when`(factory.isSupported(Mockito.anyInt())).thenReturn(true) val mockKit = Mockito.mock(KitIntegration::class.java) Mockito.`when`(mockKit.configuration).thenReturn( @@ -214,6 +228,7 @@ class KitManagerImplTest { KitIntegrationFactory::class.java ) manager.setKitFactory(factory) + Mockito.`when`(factory.getSupportedKits()).thenReturn(createKitsMap(listOf(1, 2, 3)).keys) Mockito.`when`(factory.isSupported(Mockito.anyInt())).thenReturn(true) val mockKit = Mockito.mock(KitIntegration::class.java) Mockito.`when`(mockKit.configuration).thenReturn( @@ -252,6 +267,7 @@ class KitManagerImplTest { KitIntegrationFactory::class.java ) manager.setKitFactory(factory) + Mockito.`when`(factory.getSupportedKits()).thenReturn(createKitsMap(listOf(1, 2, 3)).keys) Mockito.`when`(factory.isSupported(Mockito.anyInt())).thenReturn(true) val mockKit = Mockito.mock(KitIntegration::class.java) Mockito.`when`(mockKit.configuration).thenReturn( @@ -293,6 +309,7 @@ class KitManagerImplTest { KitIntegrationFactory::class.java ) manager.setKitFactory(factory) + Mockito.`when`(factory.getSupportedKits()).thenReturn(createKitsMap(listOf(1, 2, 3)).keys) Mockito.`when`(factory.isSupported(Mockito.anyInt())).thenReturn(true) val mockKit = Mockito.mock(KitIntegration::class.java) Mockito.`when`(mockKit.configuration).thenReturn( @@ -338,6 +355,7 @@ class KitManagerImplTest { KitIntegrationFactory::class.java ) manager.setKitFactory(factory) + Mockito.`when`(factory.getSupportedKits()).thenReturn(createKitsMap(listOf(1, 2, 3)).keys) Mockito.`when`(factory.isSupported(Mockito.anyInt())).thenReturn(true) val mockKit = Mockito.mock(KitIntegration::class.java) Mockito.`when`(mockKit.configuration).thenReturn( @@ -473,6 +491,112 @@ class KitManagerImplTest { TestCase.assertEquals(1, manager.logCommerceEventCalled) } + @Test + fun testMParticleConfigureKitsFromOptions() { + val sideloadedKit = Mockito.mock(MPSideloadedKit::class.java) + val kitId = 6000000 + val configJSONObj = JSONObject().apply { put("id", kitId) } + val mockedKitConfig = KitConfiguration.createKitConfiguration(configJSONObj) + Mockito.`when`(sideloadedKit.configuration).thenReturn(mockedKitConfig) + + val options = MParticleOptions.builder(MockContext()) + .sideloadedKits(mutableListOf(sideloadedKit) as List).build() + val manager: KitManagerImpl = MockKitManagerImpl(options) + val factory = Mockito.mock(KitIntegrationFactory::class.java) + manager.setKitFactory(factory) + + Mockito.`when`(factory.isSupported(Mockito.anyInt())).thenReturn(true) + val supportedKit = mutableSetOf(kitId) + Mockito.`when`(manager.supportedKits).thenReturn(supportedKit) + Mockito.`when`(sideloadedKit.isDisabled).thenReturn(false) + Mockito.`when`( + factory.createInstance( + Mockito.any( + KitManagerImpl::class.java + ), + Mockito.any(KitConfiguration::class.java) + ) + ).thenReturn(sideloadedKit) + manager.configureKits(mutableListOf(mockedKitConfig)) + Assert.assertEquals(1, manager.providers.size) + Assert.assertTrue(manager.providers.containsKey(kitId)) + } + + @Test + fun testMParticleUpdateEmptyConfigKitWithKitOptions() { + val sideloadedKit = Mockito.mock(MPSideloadedKit::class.java) + val kitId = 6000000 + val configJSONObj = JSONObject().apply { put("id", kitId) } + val mockedKitConfig = KitConfiguration.createKitConfiguration(configJSONObj) + Mockito.`when`(sideloadedKit.configuration).thenReturn(mockedKitConfig) + + val options = MParticleOptions.builder(MockContext()) + .sideloadedKits(mutableListOf(sideloadedKit) as List).build() + val manager: KitManagerImpl = MockKitManagerImpl(options) + val factory = Mockito.mock(KitIntegrationFactory::class.java) + Mockito.`when`(factory.getSupportedKits()).thenReturn(createKitsMap(listOf(kitId), MPSideloadedKit::class.java).keys) + manager.setKitFactory(factory) + + Mockito.`when`(factory.isSupported(Mockito.anyInt())).thenReturn(true) + val supportedKit = mutableSetOf(kitId) + Mockito.`when`(manager.supportedKits).thenReturn(supportedKit) + Mockito.`when`(sideloadedKit.isDisabled).thenReturn(false) + Mockito.`when`( + factory.createInstance( + Mockito.any( + KitManagerImpl::class.java + ), + Mockito.any(KitConfiguration::class.java) + ) + ).thenReturn(sideloadedKit) + manager.configureKits(mutableListOf(mockedKitConfig)) + Assert.assertEquals(1, manager.providers.size) + Assert.assertTrue(manager.providers.containsKey(kitId)) + + manager.updateKits(JSONArray()) + Assert.assertEquals(0, manager.providers.size) + Assert.assertFalse(manager.providers.containsKey(kitId)) + } + + @Test + fun testSideloadedKitAdded() { + val manager: KitManagerImpl = MockKitManagerImpl() + val idOne = 6000000 + val idTwo = 6000001 + val kitConfiguration = JSONArray() + .apply { + put(JSONObject().apply { put("id", 1) }) + put(JSONObject().apply { put("id", idOne) }) + put(JSONObject().apply { put("id", idTwo) }) + } + Mockito.`when`(manager.mCoreCallbacks.latestKitConfiguration).thenReturn(kitConfiguration) + val factory = Mockito.mock( + KitIntegrationFactory::class.java + ) + manager.setKitFactory(factory) + Mockito.`when`(factory.getSupportedKits()).thenReturn(createKitsMap(listOf(1, idOne, idTwo), MPSideloadedKit::class.java).keys) + Mockito.`when`(factory.isSupported(Mockito.anyInt())).thenReturn(true) + val sideloadedKit = Mockito.mock(KitIntegration::class.java) + Mockito.`when`(sideloadedKit.isDisabled).thenReturn(false) + Mockito.`when`(sideloadedKit.configuration).thenReturn( + Mockito.mock( + KitConfiguration::class.java + ) + ) + Mockito.`when`( + factory.createInstance( + Mockito.any( + KitManagerImpl::class.java + ), + Mockito.any(KitConfiguration::class.java) + ) + ).thenReturn(sideloadedKit) + manager.updateKits(kitConfiguration) + Assert.assertEquals(3, manager.providers.size) + Assert.assertTrue(manager.providers.containsKey(idOne)) + Assert.assertTrue(manager.providers.containsKey(idOne)) + } + @Test @Throws(Exception::class) fun testShouldEnableKitOnOptIn() { @@ -489,6 +613,7 @@ class KitManagerImplTest { KitIntegrationFactory::class.java ) manager.setKitFactory(factory) + Mockito.`when`(factory.getSupportedKits()).thenReturn(createKitsMap(listOf(1, 2)).keys) Mockito.`when`(factory.isSupported(Mockito.anyInt())).thenReturn(true) val mockKit = Mockito.mock(KitIntegration::class.java) Mockito.`when`(mockKit.isDisabled).thenReturn(true) diff --git a/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerTest.kt b/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerTest.kt index b688440e0..8087b7ec0 100644 --- a/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerTest.kt +++ b/android-kit-base/src/test/kotlin/com/mparticle/kits/KitManagerTest.kt @@ -6,6 +6,7 @@ import android.net.Uri import android.os.Looper import com.mparticle.MPEvent import com.mparticle.MParticle +import com.mparticle.MParticleOptions import com.mparticle.internal.ConfigManager import com.mparticle.internal.KitManager.KitStatus import com.mparticle.mock.MockContext @@ -36,7 +37,8 @@ class KitManagerTest { MParticle.setInstance(mockMp) manager = MockKitManagerImpl(MockContext(), null, MockCoreCallbacks()) Assert.assertNotNull(manager.providers) - val mockKitFactory = MockKitIntegrationFactory() + val mockKitFactory = + MockKitIntegrationFactory(MParticleOptions.builder(MockContext()).build()) manager.setKitFactory(mockKitFactory) } @@ -54,6 +56,14 @@ class KitManagerTest { manager.updateKits(JSONArray()) Assert.assertNotNull(manager.providers) val array = configJson.optJSONArray(ConfigManager.KEY_EMBEDDED_KITS) + manager.mKitIntegrationFactory.supportedKits.putAll( + hashMapOf>( + Pair(37, KitIntegration::class.java), + Pair(56, KitIntegration::class.java), + Pair(64, KitIntegration::class.java), + Pair(68, KitIntegration::class.java) + ) + ) Assert.assertNotNull(array) manager.updateKits(array) val providers = manager.providers @@ -136,6 +146,14 @@ class KitManagerTest { Mockito.`when`(Looper.getMainLooper()).thenReturn(looper) val configJson = JSONObject(TestConstants.SAMPLE_EK_CONFIG) val array = configJson.optJSONArray(ConfigManager.KEY_EMBEDDED_KITS) + manager.mKitIntegrationFactory.supportedKits.putAll( + hashMapOf>( + Pair(37, KitIntegration::class.java), + Pair(56, KitIntegration::class.java), + Pair(64, KitIntegration::class.java), + Pair(68, KitIntegration::class.java) + ) + ) manager.updateKits(array) val kitStatus = manager.kitStatus val testIds = arrayOf("56", "64", "37", "68") diff --git a/testutils/src/main/java/com/mparticle/mock/MockConfigManager.java b/testutils/src/main/java/com/mparticle/mock/MockConfigManager.java index 91f928ec2..4cc2975b0 100644 --- a/testutils/src/main/java/com/mparticle/mock/MockConfigManager.java +++ b/testutils/src/main/java/com/mparticle/mock/MockConfigManager.java @@ -6,6 +6,6 @@ public class MockConfigManager extends ConfigManager { public MockConfigManager() { - super(new MockContext(), MParticle.Environment.Production, null, null, null, null, null, null,null); + super(new MockContext(), MParticle.Environment.Production, null, null, null, null, null, null,null, null); } } diff --git a/testutils/src/main/java/com/mparticle/mock/MockKitIntegrationFactory.java b/testutils/src/main/java/com/mparticle/mock/MockKitIntegrationFactory.java index 6ba9d4ee6..375e51a70 100644 --- a/testutils/src/main/java/com/mparticle/mock/MockKitIntegrationFactory.java +++ b/testutils/src/main/java/com/mparticle/mock/MockKitIntegrationFactory.java @@ -1,5 +1,6 @@ package com.mparticle.mock; +import com.mparticle.MParticleOptions; import com.mparticle.kits.KitIntegration; import com.mparticle.kits.KitIntegrationFactory; import com.mparticle.kits.KitManagerImpl; @@ -8,6 +9,10 @@ public class MockKitIntegrationFactory extends KitIntegrationFactory { + public MockKitIntegrationFactory(MParticleOptions options) { + super(options); + } + @Override public boolean isSupported(int kitModuleId) { return true; diff --git a/testutils/src/main/java/com/mparticle/mock/MockKitManagerImpl.java b/testutils/src/main/java/com/mparticle/mock/MockKitManagerImpl.java index 98e1570ba..1e25a5ef1 100644 --- a/testutils/src/main/java/com/mparticle/mock/MockKitManagerImpl.java +++ b/testutils/src/main/java/com/mparticle/mock/MockKitManagerImpl.java @@ -2,6 +2,7 @@ import android.content.Context; +import com.mparticle.MParticle; import com.mparticle.MParticleOptions; import com.mparticle.internal.CoreCallbacks; import com.mparticle.internal.ReportingManager; @@ -23,6 +24,11 @@ public MockKitManagerImpl(Context context, ReportingManager reportingManager, Co super(context, reportingManager, coreCallbacks, Mockito.mock(MParticleOptions.class)); } + public MockKitManagerImpl( MParticleOptions options) { + super(new MockContext(), Mockito.mock(ReportingManager.class), Mockito.mock(CoreCallbacks.class), options); + Mockito.when(mCoreCallbacks.getKitListener()).thenReturn(CoreCallbacks.KitListener.EMPTY); + } + @Override protected KitConfiguration createKitConfiguration(JSONObject configuration) throws JSONException { return MockKitConfiguration.createKitConfiguration(configuration);