From 3a5292ed177055e47b04e5784dc51ec2a9497a1e Mon Sep 17 00:00:00 2001 From: Mansi Pandya Date: Fri, 24 Jan 2025 11:32:09 -0500 Subject: [PATCH 1/2] refactor: Migrate Internal Config class to kotlin --- .../kotlin/com.mparticle/MParticleTest.kt | 12 +- .../internal/ConfigManagerInstrumentedTest.kt | 43 +- .../internal/ConfigMigrationTest.kt | 6 +- .../internal/ConfigStalenessCheckTest.kt | 10 +- .../com/mparticle/internal/ConfigManager.java | 1400 ---------------- .../com/mparticle/internal/ConfigManager.kt | 1436 +++++++++++++++++ .../mparticle/internal/ConfigManagerTest.kt | 92 +- .../mparticle/internal/MessageManagerTest.kt | 28 +- 8 files changed, 1539 insertions(+), 1488 deletions(-) delete mode 100644 android-core/src/main/java/com/mparticle/internal/ConfigManager.java create mode 100644 android-core/src/main/kotlin/com/mparticle/internal/ConfigManager.kt diff --git a/android-core/src/androidTest/kotlin/com.mparticle/MParticleTest.kt b/android-core/src/androidTest/kotlin/com.mparticle/MParticleTest.kt index fce254595..cc9f3baa5 100644 --- a/android-core/src/androidTest/kotlin/com.mparticle/MParticleTest.kt +++ b/android-core/src/androidTest/kotlin/com.mparticle/MParticleTest.kt @@ -229,19 +229,19 @@ class MParticleTest : BaseCleanStartedEachTest() { startMParticle() MParticle.getInstance()!!.Messaging().enablePushNotifications(senderId) var fetchedSenderId: String? = - MParticle.getInstance()!!.mInternal.getConfigManager().getPushSenderId() + MParticle.getInstance()!!.mInternal.getConfigManager().pushSenderId Assert.assertTrue( - MParticle.getInstance()!!.mInternal.getConfigManager().isPushEnabled() ?: false + MParticle.getInstance()!!.mInternal.getConfigManager().isPushEnabled ?: false ) Assert.assertEquals(senderId, fetchedSenderId) val otherSenderId = "senderIdLogPushRegistration" MParticle.getInstance()!!.logPushRegistration("instanceId", otherSenderId) - fetchedSenderId = MParticle.getInstance()!!.mInternal.getConfigManager().getPushSenderId() + fetchedSenderId = MParticle.getInstance()!!.mInternal.getConfigManager().pushSenderId Assert.assertEquals(otherSenderId, fetchedSenderId) MParticle.getInstance()!!.Messaging().disablePushNotifications() - fetchedSenderId = MParticle.getInstance()!!.mInternal.getConfigManager().getPushSenderId() + fetchedSenderId = MParticle.getInstance()!!.mInternal.getConfigManager().pushSenderId Assert.assertFalse( - MParticle.getInstance()!!.mInternal.getConfigManager().isPushEnabled() ?: false + MParticle.getInstance()!!.mInternal.getConfigManager().isPushEnabled ?: false ) Assert.assertNull(fetchedSenderId) } @@ -342,7 +342,7 @@ class MParticleTest : BaseCleanStartedEachTest() { Assert.assertTrue(databaseJson.getJSONArray("messages").length() > 0) Assert.assertEquals(6, allTables.size.toLong()) Assert.assertTrue( - 10 < (MParticle.getInstance()!!.mInternal.getConfigManager().getMpids()?.size ?: 0) + 10 < (MParticle.getInstance()!!.mInternal.getConfigManager().mpids?.size ?: 0) ) // Set strict mode, so if we get any warning or error messages during the reset/restart phase, diff --git a/android-core/src/androidTest/kotlin/com.mparticle/internal/ConfigManagerInstrumentedTest.kt b/android-core/src/androidTest/kotlin/com.mparticle/internal/ConfigManagerInstrumentedTest.kt index cd4680d43..385e0024e 100644 --- a/android-core/src/androidTest/kotlin/com.mparticle/internal/ConfigManagerInstrumentedTest.kt +++ b/android-core/src/androidTest/kotlin/com.mparticle/internal/ConfigManagerInstrumentedTest.kt @@ -131,35 +131,38 @@ class ConfigManagerInstrumentedTest : BaseAbstractTest() { val loadedKitLocal = AndroidUtils.Mutable(false) setCachedConfig(simpleConfigWithKits) mServer.setupConfigDeferred() - val configLoadedListener = ConfigLoadedListener { configType, isNew -> - if (!isNew) { - when (configType) { - ConfigType.CORE -> { - if (loadedCoreLocal.value) { - Assert.fail("core config already loaded") - } else { - Logger.error("LOADED CACHED Core") - loadedCoreLocal.value = true + val configLoadedListener = object : ConfigLoadedListener { + override fun onConfigUpdated(configType: ConfigType, isNew: Boolean) { + if (!isNew) { + when (configType) { + ConfigType.CORE -> { + if (loadedCoreLocal.value) { + Assert.fail("core config already loaded") + } else { + Logger.error("LOADED CACHED Core") + loadedCoreLocal.value = true + } + if (loadedKitLocal.value) { + Assert.fail("kit config already loaded") + } else { + Logger.error("LOADED CACHED Kit") + loadedKitLocal.value = true + } } - if (loadedKitLocal.value) { + + ConfigType.KIT -> if (loadedKitLocal.value) { Assert.fail("kit config already loaded") } else { Logger.error("LOADED CACHED Kit") loadedKitLocal.value = true } } - ConfigType.KIT -> if (loadedKitLocal.value) { - Assert.fail("kit config already loaded") - } else { - Logger.error("LOADED CACHED Kit") - loadedKitLocal.value = true - } } + if (loadedCoreLocal.value && loadedKitLocal.value) { + latch.countDown() + } + Logger.error("KIT = " + loadedKitLocal.value + " Core: " + loadedCoreLocal.value) } - if (loadedCoreLocal.value && loadedKitLocal.value) { - latch.countDown() - } - Logger.error("KIT = " + loadedKitLocal.value + " Core: " + loadedCoreLocal.value) } val options = MParticleOptions.builder(mContext) .credentials("key", "secret") 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 7d4587905..79864ce2d 100644 --- a/android-core/src/androidTest/kotlin/com.mparticle/internal/ConfigMigrationTest.kt +++ b/android-core/src/androidTest/kotlin/com.mparticle/internal/ConfigMigrationTest.kt @@ -124,8 +124,8 @@ class ConfigMigrationTest : BaseCleanInstallEachTest() { } private fun setOldConfigState(config: JSONObject) { - ConfigManager.getInstance(mContext).getKitConfigPreferences().edit() - .remove(ConfigManager.KIT_CONFIG_KEY) + ConfigManager.getInstance(mContext).kitConfigPreferences?.edit() + ?.remove(ConfigManager.KIT_CONFIG_KEY) ConfigManager.getPreferences(mContext).edit() .putString(oldConfigSharedprefsKey, config.toString()).apply() } @@ -144,7 +144,7 @@ class ConfigMigrationTest : BaseCleanInstallEachTest() { assertNull(JSONObject(configString).optJSONArray(ConfigManager.KEY_EMBEDDED_KITS)) assertEquals( config.optString(ConfigManager.KEY_EMBEDDED_KITS, JSONArray().toString()), - ConfigManager.getInstance(mContext).kitConfigPreferences.getString( + 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 907ec80ea..d91615ee6 100644 --- a/android-core/src/androidTest/kotlin/com.mparticle/internal/ConfigStalenessCheckTest.kt +++ b/android-core/src/androidTest/kotlin/com.mparticle/internal/ConfigStalenessCheckTest.kt @@ -189,10 +189,12 @@ class ConfigStalenessCheckTest : BaseCleanInstallEachTest() { fun MParticleOptions.Builder.addCredentials() = this.credentials("apiKey", "apiSecret") fun ConfigManager.onNewConfig(callback: () -> Unit) { - addConfigUpdatedListener { configType, isNew -> - if (isNew && configType == ConfigManager.ConfigType.CORE) { - callback() + addConfigUpdatedListener(object : ConfigManager.ConfigLoadedListener { + override fun onConfigUpdated(configType: ConfigManager.ConfigType, isNew: Boolean) { + if (isNew && configType == ConfigManager.ConfigType.CORE) { + callback() + } } - } + }) } } diff --git a/android-core/src/main/java/com/mparticle/internal/ConfigManager.java b/android-core/src/main/java/com/mparticle/internal/ConfigManager.java deleted file mode 100644 index 143f2822d..000000000 --- a/android-core/src/main/java/com/mparticle/internal/ConfigManager.java +++ /dev/null @@ -1,1400 +0,0 @@ -package com.mparticle.internal; - -import android.content.Context; -import android.content.SharedPreferences; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.os.Build; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.WorkerThread; - -import com.mparticle.Configuration; -import com.mparticle.ExceptionHandler; -import com.mparticle.MParticle; -import com.mparticle.MParticleOptions; -import com.mparticle.consent.ConsentState; -import com.mparticle.identity.IdentityApi; -import com.mparticle.internal.messages.BaseMPMessage; -import com.mparticle.networking.NetworkOptions; -import com.mparticle.networking.NetworkOptionsManager; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.TimeUnit; - -public class ConfigManager { - public static final String CONFIG_JSON = "json"; - public static final String KIT_CONFIG_PREFERENCES = "mparticle_config.json"; - public static final String CONFIG_JSON_TIMESTAMP = "json_timestamp"; - private static final String KEY_TRIGGER_ITEMS = "tri"; - private static final String KEY_MESSAGE_MATCHES = "mm"; - private static final String KEY_TRIGGER_ITEM_HASHES = "evts"; - private static final String KEY_INFLUENCE_OPEN = "pio"; - static final String KEY_OPT_OUT = "oo"; - public static final String KEY_UNHANDLED_EXCEPTIONS = "cue"; - public static final String KEY_PUSH_MESSAGES = "pmk"; - public static final String KEY_DIRECT_URL_ROUTING = "dur"; - public static final String KEY_EMBEDDED_KITS = "eks"; - static final String KEY_UPLOAD_INTERVAL = "uitl"; - static final String KEY_SESSION_TIMEOUT = "stl"; - public static final String VALUE_APP_DEFINED = "appdefined"; - public static final String VALUE_CUE_CATCH = "forcecatch"; - public static final String PREFERENCES_FILE = "mp_preferences"; - private static final String KEY_DEVICE_PERFORMANCE_METRICS_DISABLED = "dpmd"; - public static final String WORKSPACE_TOKEN = "wst"; - static final String ALIAS_MAX_WINDOW = "alias_max_window"; - static final String KEY_RAMP = "rp"; - static final String DATAPLAN_KEY = "dpr"; - static final String DATAPLAN_OBJ = "dtpn"; - static final String DATAPLAN_BLOCKING = "blok"; - static final String DATAPLAN_VERSION = "vers"; - static final String DATAPLAN_BLOCK_EVENTS = "ev"; - static final String DATAPLAN_BLOCK_EVENT_ATTRIBUTES = "ea"; - static final String DATAPLAN_BLOCK_USER_ATTRIBUTES = "ua"; - static final String DATAPLAN_BLOCK_USER_IDENTITIES = "id"; - 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; - private static final int DEFAULT_MAX_ALIAS_WINDOW_DAYS = 90; - private Context mContext; - private static NetworkOptions sNetworkOptions; - private boolean mIgnoreDataplanOptionsFromConfig = false; - private MParticleOptions.DataplanOptions mDataplanOptions; - - static SharedPreferences sPreferences; - - private static JSONArray sPushKeys; - private boolean directUrlRouting = false; - private UserStorage mUserStorage; - private String mLogUnhandledExceptions = VALUE_APP_DEFINED; - - private boolean mSendOoEvents; - private JSONObject mProviderPersistence; - private int mRampValue = -1; - private int mUserBucket = -1; - - private int mSessionTimeoutInterval = -1; - private int mUploadInterval = -1; - private long mInfluenceOpenTimeout = 3600 * 1000; - private JSONArray mTriggerMessageMatches, mTriggerMessageHashes = null; - private ExceptionHandler mExHandler; - private JSONObject mCurrentCookies; - private String mDataplanId; - private Integer mDataplanVersion; - private Integer mMaxConfigAge; - public static final int DEFAULT_CONNECTION_TIMEOUT_SECONDS = 30; - public static final int MINIMUM_CONNECTION_TIMEOUT_SECONDS = 1; - 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(); - } - - public static ConfigManager getInstance(Context context) { - ConfigManager configManager = null; - MParticle mParticle = MParticle.getInstance(); - if (mParticle != null) { - configManager = MParticle.getInstance().Internal().getConfigManager(); - } - if (configManager == null) { - configManager = new ConfigManager(context); - } - return configManager; - } - - public ConfigManager(Context context) { - mContext = context; - sPreferences = getPreferences(mContext); - } - - 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), 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, @Nullable List sideloadedKits) { - mContext = context.getApplicationContext(); - sPreferences = getPreferences(mContext); - if (apiKey != null || apiSecret != null) { - setCredentials(apiKey, apiSecret); - } - if (environment != null) { - setEnvironment(environment); - } - mUserStorage = UserStorage.create(mContext, getMpid()); - //if we are initialized with a DataplanOptions instance, then we will ignore values from remote config - mIgnoreDataplanOptionsFromConfig = dataplanOptions != null; - mDataplanOptions = dataplanOptions; - 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); - } - } - } - - public void onMParticleStarted() { - checkConfigStaleness(); - migrateConfigIfNeeded(); - restoreCoreConfig(); - } - - private void restoreCoreConfig() { - String oldConfig = getConfig(); - if (!MPUtility.isEmpty(oldConfig)) { - try { - JSONObject oldConfigJson = new JSONObject(oldConfig); - reloadCoreConfig(oldConfigJson); - } catch (Exception jse) { - - } - } - } - - /** - * This called on startup. The only thing that's completely necessary is that we fire up kits. - */ - @Nullable - @WorkerThread - public JSONArray getLatestKitConfiguration() { - String oldConfig = getKitConfigPreferences().getString(KIT_CONFIG_KEY, null); - if (!MPUtility.isEmpty(oldConfig)) { - try { - return new JSONArray(oldConfig); - } catch (Exception jse) { - - } - } - return null; - } - - public MParticleOptions.DataplanOptions getDataplanOptions() { - return mDataplanOptions; - } - - public UserStorage getUserStorage() { - return getUserStorage(getMpid()); - } - - public UserStorage getUserStorage(long mpId) { - if (mUserStorage == null || mUserStorage.getMpid() != mpId) { - mUserStorage = UserStorage.create(mContext, mpId); - } - return mUserStorage; - } - - public static UserStorage getUserStorage(Context context) { - return UserStorage.create(context, getMpid(context)); - } - - public static UserStorage getUserStorage(Context context, long mpid) { - return UserStorage.create(context, mpid); - } - - public void deleteUserStorage(Context context, long mpid) { - if (mUserStorage != null) { - mUserStorage.deleteUserConfig(context, mpid); - } - } - - public void deleteUserStorage(long mpId) { - deleteUserStorage(mContext, mpId); - } - - static void deleteConfigManager(Context context) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - context.deleteSharedPreferences(PREFERENCES_FILE); - sPreferences = context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE); - } else { - if (sPreferences == null) { - sPreferences = context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE); - } - sPreferences.edit().clear().commit(); - } - } - - void migrateConfigIfNeeded() { - if (!sPreferences.getBoolean(MIGRATED_TO_KIT_SHARED_PREFS, false)) { - sPreferences.edit().putBoolean(MIGRATED_TO_KIT_SHARED_PREFS, true).apply(); - String configString = sPreferences.getString(CONFIG_JSON, null); - if (!MPUtility.isEmpty(configString)) { - try { - //save ourselves some time and only parse the JSONObject if might contain the embedded kits key - if (configString.contains("\"" + KEY_EMBEDDED_KITS + "\":")) { - Logger.info("Migrating kit configuration"); - saveConfigJson(new JSONObject(configString), getEtag(), getIfModified(), getConfigTimestamp()); - } - } catch (JSONException jse) { - - } - } - } - } - - /** - * detrmine if the stored config age is greater than mMaxConfigAge and clear it from storage if it is - */ - void checkConfigStaleness() { - Long storageDate = getConfigTimestamp(); - if (storageDate == null) { - // migration step: if the current config does not have a timestamp, set one to the current time - setConfigTimestamp(System.currentTimeMillis()); - storageDate = getConfigTimestamp(); - } - if (mMaxConfigAge == null || mMaxConfigAge < 0) { - return; - } - if (mMaxConfigAge == 0) { - clearConfig(); - } else { - if (System.currentTimeMillis() >= storageDate + TimeUnit.SECONDS.toMillis(mMaxConfigAge)) { - clearConfig(); - } - } - } - - @Nullable - String getConfig() { - return sPreferences.getString(CONFIG_JSON, ""); - } - - void setConfigTimestamp(Long timestamp) { - sPreferences.edit() - .putLong(CONFIG_JSON_TIMESTAMP, timestamp) - .apply(); - } - - @Nullable - Long getConfigTimestamp() { - if (sPreferences.contains(CONFIG_JSON_TIMESTAMP)) { - return sPreferences.getLong(CONFIG_JSON_TIMESTAMP, 0); - } else { - return null; - } - } - - public void saveConfigJson(JSONObject combinedConfig) throws JSONException { - saveConfigJson(combinedConfig, null, null, null); - } - - public void saveConfigJson(JSONObject combinedConfig, String etag, String lastModified, Long timestamp) throws JSONException { - if (combinedConfig != null) { - JSONArray kitConfig = combinedConfig.has(KEY_EMBEDDED_KITS) ? (JSONArray) combinedConfig.remove(KEY_EMBEDDED_KITS) : null; - saveConfigJson(combinedConfig, kitConfig, etag, lastModified, timestamp); - } else { - saveConfigJson(combinedConfig, null, etag, lastModified, timestamp); - } - } - - void saveConfigJson(JSONObject coreConfig, JSONArray kitConfig, String etag, String lastModified, Long timestamp) throws JSONException { - if (coreConfig != null) { - String kitConfigString = kitConfig != null ? kitConfig.toString() : null; - Logger.debug("Updating core config to:\n" + coreConfig); - Logger.debug("Updating kit config to:\n" + kitConfigString); - sPreferences.edit() - .putString(CONFIG_JSON, coreConfig.toString()) - .putLong(CONFIG_JSON_TIMESTAMP, timestamp != null ? timestamp : System.currentTimeMillis()) - .putString(Constants.PrefKeys.ETAG, etag) - .putString(Constants.PrefKeys.IF_MODIFIED, lastModified) - .apply(); - getKitConfigPreferences() - .edit() - .putString(KIT_CONFIG_KEY, SideloadedKitsUtils.INSTANCE.combineConfig(kitConfig, sideloadedKits).toString()) - .apply(); - } else { - Logger.debug("clearing current configurations"); - clearConfig(); - } - } - - void clearConfig() { - sPreferences.edit() - .remove(CONFIG_JSON) - .remove(CONFIG_JSON_TIMESTAMP) - .remove(Constants.PrefKeys.ETAG) - .remove(Constants.PrefKeys.IF_MODIFIED) - .apply(); - getKitConfigPreferences() - .edit() - .remove(KIT_CONFIG_KEY) - .apply(); - } - - public synchronized void updateConfig(JSONObject responseJSON) throws JSONException { - updateConfig(responseJSON, null, null); - } - - 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(); - } - JSONArray kitConfig = responseJSON.has(KEY_EMBEDDED_KITS) ? (JSONArray) responseJSON.remove(KEY_EMBEDDED_KITS) : null; - saveConfigJson(responseJSON, kitConfig, etag, lastModified, System.currentTimeMillis()); - updateCoreConfig(responseJSON, true); - updateKitConfig(kitConfig); - } - - public synchronized void reloadCoreConfig(JSONObject responseJSON) throws JSONException { - updateCoreConfig(responseJSON, false); - } - - private synchronized void updateKitConfig(@Nullable JSONArray kitConfigs) { - MParticle instance = MParticle.getInstance(); - if (instance != null) { - instance.Internal().getKitManager() - .updateKits(kitConfigs) - .onKitsLoaded(() -> onConfigLoaded(ConfigType.KIT, true)); - } - } - - 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); - } - - if (responseJSON.has(KEY_PUSH_MESSAGES) && newConfig) { - sPushKeys = responseJSON.getJSONArray(KEY_PUSH_MESSAGES); - editor.putString(KEY_PUSH_MESSAGES, sPushKeys.toString()); - } - - if (responseJSON.has(KEY_DIRECT_URL_ROUTING)) { - directUrlRouting = responseJSON.optBoolean(KEY_DIRECT_URL_ROUTING); - editor.putBoolean(KEY_DIRECT_URL_ROUTING, directUrlRouting); - } - - mRampValue = responseJSON.optInt(KEY_RAMP, -1); - - if (responseJSON.has(KEY_OPT_OUT)) { - mSendOoEvents = responseJSON.getBoolean(KEY_OPT_OUT); - } else { - mSendOoEvents = false; - } - - if (responseJSON.has(ProviderPersistence.KEY_PERSISTENCE)) { - setProviderPersistence(new ProviderPersistence(responseJSON, mContext)); - } else { - setProviderPersistence(null); - } - - mSessionTimeoutInterval = responseJSON.optInt(KEY_SESSION_TIMEOUT, -1); - mUploadInterval = responseJSON.optInt(KEY_UPLOAD_INTERVAL, -1); - - mTriggerMessageMatches = null; - mTriggerMessageHashes = null; - if (responseJSON.has(KEY_TRIGGER_ITEMS)) { - try { - JSONObject items = responseJSON.getJSONObject(KEY_TRIGGER_ITEMS); - if (items.has(KEY_MESSAGE_MATCHES)) { - mTriggerMessageMatches = items.getJSONArray(KEY_MESSAGE_MATCHES); - } - if (items.has(KEY_TRIGGER_ITEM_HASHES)) { - mTriggerMessageHashes = items.getJSONArray(KEY_TRIGGER_ITEM_HASHES); - } - } catch (JSONException jse) { - - } - - } - - if (responseJSON.has(KEY_INFLUENCE_OPEN)) { - mInfluenceOpenTimeout = responseJSON.getLong(KEY_INFLUENCE_OPEN) * 60 * 1000; - } else { - mInfluenceOpenTimeout = 30 * 60 * 1000; - } - - if (responseJSON.has(KEY_DEVICE_PERFORMANCE_METRICS_DISABLED)) { - MessageManager.devicePerformanceMetricsDisabled = responseJSON.optBoolean(KEY_DEVICE_PERFORMANCE_METRICS_DISABLED, false); - } - if (responseJSON.has(WORKSPACE_TOKEN)) { - editor.putString(WORKSPACE_TOKEN, responseJSON.getString(WORKSPACE_TOKEN)); - } else { - editor.remove(WORKSPACE_TOKEN); - } - if (responseJSON.has(ALIAS_MAX_WINDOW)) { - editor.putInt(ALIAS_MAX_WINDOW, responseJSON.getInt(ALIAS_MAX_WINDOW)); - } else { - editor.remove(ALIAS_MAX_WINDOW); - } - if (!mIgnoreDataplanOptionsFromConfig) { - mDataplanOptions = parseDataplanOptions(responseJSON); - MParticle instance = MParticle.getInstance(); - if (instance != null) { - instance.Internal().getKitManager().updateDataplan(mDataplanOptions); - } - } - editor.apply(); - applyConfig(); - onConfigLoaded(ConfigType.CORE, newConfig); - } - - public String getActiveModuleIds() { - Map kitStatusMap = MParticle.getInstance().Internal().getKitManager().getKitStatus(); - List activeKits = new ArrayList<>(); - for (Map.Entry kitStatus : kitStatusMap.entrySet()) { - KitManager.KitStatus status = kitStatus.getValue(); - switch (status) { - case ACTIVE: - case STOPPED: - activeKits.add(kitStatus.getKey()); - } - } - Collections.sort(activeKits); - if (activeKits.size() == 0) { - return ""; - } else { - StringBuilder builder = new StringBuilder(activeKits.size() * 3); - for (Integer kitId : activeKits) { - builder.append(kitId); - builder.append(","); - } - builder.deleteCharAt(builder.length() - 1); - return builder.toString(); - } - } - - /** - * When the Config manager starts up, we don't want to enable everything immediately to save on app-load time. - * This method will be called from a background thread after startup is already complete. - */ - public void delayedStart() { - String senderId = getPushSenderId(); - if (isPushEnabled() && senderId != null) { - MParticle.getInstance().Messaging().enablePushNotifications(senderId); - } - } - - public JSONArray getTriggerMessageMatches() { - return mTriggerMessageMatches; - } - - public long getInfluenceOpenTimeoutMillis() { - return mInfluenceOpenTimeout; - } - - private void applyConfig() { - if (getLogUnhandledExceptions()) { - enableUncaughtExceptionLogging(false); - } else { - disableUncaughtExceptionLogging(false); - } - } - - public void enableUncaughtExceptionLogging(boolean userTriggered) { - if (userTriggered) { - setLogUnhandledExceptions(true); - } - if (null == mExHandler) { - Thread.UncaughtExceptionHandler currentUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); - if (!(currentUncaughtExceptionHandler instanceof ExceptionHandler)) { - mExHandler = new ExceptionHandler(currentUncaughtExceptionHandler); - Thread.setDefaultUncaughtExceptionHandler(mExHandler); - } - } - } - - public void disableUncaughtExceptionLogging(boolean userTriggered) { - if (userTriggered) { - setLogUnhandledExceptions(false); - } - if (null != mExHandler) { - Thread.UncaughtExceptionHandler currentUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); - if (currentUncaughtExceptionHandler instanceof ExceptionHandler) { - Thread.setDefaultUncaughtExceptionHandler(mExHandler.getOriginalExceptionHandler()); - mExHandler = null; - } - } - } - - public boolean getLogUnhandledExceptions() { - if (VALUE_APP_DEFINED.equals(mLogUnhandledExceptions)) { - return sPreferences.getBoolean(Constants.PrefKeys.REPORT_UNCAUGHT_EXCEPTIONS, false); - } else { - return VALUE_CUE_CATCH.equals(mLogUnhandledExceptions); - } - } - - public void setLogUnhandledExceptions(boolean log) { - sPreferences.edit().putBoolean(Constants.PrefKeys.REPORT_UNCAUGHT_EXCEPTIONS, log).apply(); - } - - public String getApiKey() { - return sPreferences.getString(Constants.PrefKeys.API_KEY, null); - } - - public String getApiSecret() { - return sPreferences.getString(Constants.PrefKeys.API_SECRET, null); - } - - public void setCredentials(String apiKey, String secret) { - sPreferences.edit() - .putString(Constants.PrefKeys.API_KEY, apiKey) - .putString(Constants.PrefKeys.API_SECRET, secret) - .apply(); - } - - public long getUploadInterval() { - if (getEnvironment().equals(MParticle.Environment.Development)) { - return DEVMODE_UPLOAD_INTERVAL_MILLISECONDS; - } else { - if (mUploadInterval > 0) { - return 1000 * mUploadInterval; - } else { - return (1000 * sPreferences.getInt(Constants.PrefKeys.UPLOAD_INTERVAL, DEFAULT_UPLOAD_INTERVAL)); - } - } - } - - public void setEnvironment(MParticle.Environment environment) { - if (environment != null) { - sPreferences.edit().putInt(Constants.PrefKeys.ENVIRONMENT, environment.getValue()).apply(); - } else { - sPreferences.edit().remove(Constants.PrefKeys.ENVIRONMENT).apply(); - } - } - - public static MParticle.Environment getEnvironment() { - if (sPreferences != null) { - int env = sPreferences.getInt(Constants.PrefKeys.ENVIRONMENT, MParticle.Environment.Production.getValue()); - for (MParticle.Environment environment : MParticle.Environment.values()) { - if (environment.getValue() == env) { - return environment; - } - } - } - return MParticle.Environment.Production; - } - - public void setUploadInterval(int uploadInterval) { - sPreferences.edit().putInt(Constants.PrefKeys.UPLOAD_INTERVAL, uploadInterval).apply(); - } - - public int getSessionTimeout() { - if (mSessionTimeoutInterval > 0) { - return mSessionTimeoutInterval * 1000; - } else { - return sPreferences.getInt(Constants.PrefKeys.SESSION_TIMEOUT, DEFAULT_SESSION_TIMEOUT_SECONDS) * 1000; - } - } - - public void setSessionTimeout(int sessionTimeout) { - sPreferences.edit().putInt(Constants.PrefKeys.SESSION_TIMEOUT, sessionTimeout).apply(); - } - - public boolean isPushEnabled() { - return sPreferences.getBoolean(Constants.PrefKeys.PUSH_ENABLED, false) && getPushSenderId() != null; - } - - public String getPushSenderId() { - PushRegistrationHelper.PushRegistration pushRegistration = getPushRegistration(); - if (pushRegistration != null) { - return pushRegistration.senderId; - } else { - return null; - } - } - - public @Nullable String getPushInstanceId() { - PushRegistrationHelper.PushRegistration pushRegistration = getPushRegistration(); - if (pushRegistration != null) { - return pushRegistration.instanceId; - } else { - return null; - } - } - - public PushRegistrationHelper.PushRegistration getPushRegistration() { - String senderId = sPreferences.getString(Constants.PrefKeys.PUSH_SENDER_ID, null); - String instanceId = sPreferences.getString(Constants.PrefKeys.PUSH_INSTANCE_ID, null); - return new PushRegistrationHelper.PushRegistration(instanceId, senderId); - } - - public void setPushRegistrationFetched() { - int appVersion = getAppVersion(); - sPreferences.edit() - .putInt(Constants.PrefKeys.PROPERTY_APP_VERSION, appVersion) - .putInt(Constants.PrefKeys.PROPERTY_OS_VERSION, Build.VERSION.SDK_INT) - .apply(); - } - - public boolean isPushRegistrationFetched() { - // Check if app was updated; if so, it must clear the registration ID - // since the existing regID is not guaranteed to work with the new - // app version. - int registeredVersion = sPreferences.getInt(Constants.PrefKeys.PROPERTY_APP_VERSION, Integer.MIN_VALUE); - int currentVersion = getAppVersion(); - int osVersion = sPreferences.getInt(Constants.PrefKeys.PROPERTY_OS_VERSION, Integer.MIN_VALUE); - return registeredVersion == currentVersion && osVersion == Build.VERSION.SDK_INT; - } - - @Nullable - public String getPushInstanceIdBackground() { - return sPreferences.getString(Constants.PrefKeys.PUSH_INSTANCE_ID_BACKGROUND, null); - } - - public void setPushSenderId(String senderId) { - sPreferences.edit().putString(Constants.PrefKeys.PUSH_SENDER_ID, senderId) - .putBoolean(Constants.PrefKeys.PUSH_ENABLED, true) - .apply(); - } - - public void setPushInstanceId(@Nullable String token) { - sPreferences.edit().putString(Constants.PrefKeys.PUSH_INSTANCE_ID, token).apply(); - } - - public void setPushRegistration(PushRegistrationHelper.PushRegistration pushRegistration) { - if (pushRegistration == null || MPUtility.isEmpty(pushRegistration.senderId)) { - clearPushRegistration(); - } else { - setPushSenderId(pushRegistration.senderId); - setPushInstanceId(pushRegistration.instanceId); - } - } - - public void setPushRegistrationInBackground(PushRegistrationHelper.PushRegistration - pushRegistration) { - String oldInstanceId = getPushInstanceId(); - if (oldInstanceId == null) { - oldInstanceId = ""; - } - sPreferences.edit() - .putString(Constants.PrefKeys.PUSH_INSTANCE_ID_BACKGROUND, oldInstanceId) - .apply(); - setPushRegistration(pushRegistration); - } - - public void clearPushRegistration() { - sPreferences.edit() - .remove(Constants.PrefKeys.PUSH_SENDER_ID) - .remove(Constants.PrefKeys.PUSH_INSTANCE_ID) - .remove(Constants.PrefKeys.PUSH_ENABLED) - .remove(Constants.PrefKeys.PROPERTY_APP_VERSION) - .remove(Constants.PrefKeys.PROPERTY_OS_VERSION) - .apply(); - } - - public void clearPushRegistrationBackground() { - sPreferences.edit() - .remove(Constants.PrefKeys.PUSH_INSTANCE_ID_BACKGROUND) - .apply(); - } - - public boolean isEnabled() { - boolean optedOut = this.getOptedOut(); - return !optedOut || mSendOoEvents; - } - - public void setOptOut(boolean optOut) { - sPreferences - .edit().putBoolean(Constants.PrefKeys.OPTOUT, optOut).apply(); - } - - public boolean getOptedOut() { - return sPreferences.getBoolean(Constants.PrefKeys.OPTOUT, false); - } - - public void setPushNotificationIcon(int pushNotificationIcon) { - sPreferences.edit() - .putInt(Constants.PrefKeys.PUSH_ICON, pushNotificationIcon) - .apply(); - } - - public void setPushNotificationTitle(int pushNotificationTitle) { - sPreferences.edit() - .putInt(Constants.PrefKeys.PUSH_TITLE, pushNotificationTitle) - .apply(); - } - - public void setDisplayPushNotifications(Boolean display) { - sPreferences.edit() - .putBoolean(Constants.PrefKeys.DISPLAY_PUSH_NOTIFICATIONS, display) - .apply(); - } - - public static Boolean isDisplayPushNotifications(Context context) { - return getPreferences(context).getBoolean(Constants.PrefKeys.DISPLAY_PUSH_NOTIFICATIONS, false); - } - - static SharedPreferences getPreferences(Context context) { - return context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE); - } - - SharedPreferences getKitConfigPreferences() { - return mContext.getSharedPreferences(KIT_CONFIG_PREFERENCES, Context.MODE_PRIVATE); - } - - public static JSONArray getPushKeys(Context context) { - if (sPushKeys == null) { - String arrayString = getPreferences(context).getString(KEY_PUSH_MESSAGES, null); - try { - sPushKeys = new JSONArray(arrayString); - } catch (Exception e) { - sPushKeys = new JSONArray(); - } - } - return sPushKeys; - } - - public static int getPushTitle(Context context) { - return getPreferences(context) - .getInt(Constants.PrefKeys.PUSH_TITLE, 0); - } - - public static int getPushIcon(Context context) { - return getPreferences(context) - .getInt(Constants.PrefKeys.PUSH_ICON, 0); - } - - public static int getBreadcrumbLimit(Context context) { - return getUserStorage(context).getBreadcrumbLimit(); - } - - public static int getBreadcrumbLimit(Context context, long mpId) { - return getUserStorage(context, mpId).getBreadcrumbLimit(); - } - - public static String getCurrentUserLtv(Context context) { - return getUserStorage(context).getLtv(); - } - - public void setBreadcrumbLimit(int newLimit) { - setBreadcrumbLimit(newLimit, getMpid()); - } - - public void setBreadcrumbLimit(int newLimit, long mpId) { - getUserStorage(mpId).setBreadcrumbLimit(newLimit); - } - - public static void setNeedsToMigrate(Context context, boolean needsToMigrate) { - UserStorage.setNeedsToMigrate(context, needsToMigrate); - } - - public static void clear() { - sPreferences.edit().clear().apply(); - } - - private synchronized void setProviderPersistence(JSONObject persistence) { - mProviderPersistence = persistence; - } - - public synchronized JSONObject getProviderPersistence() { - return mProviderPersistence; - } - - public void setMpid(long newMpid, boolean isLoggedInUser) { - long previousMpid = getMpid(); - boolean currentLoggedInUser = false; - UserStorage currentUserStorage = getUserStorage(); - if (currentUserStorage != null) { - currentUserStorage.setLastSeenTime(System.currentTimeMillis()); - currentLoggedInUser = currentUserStorage.isLoggedIn(); - } - UserStorage userStorage = UserStorage.create(mContext, newMpid); - userStorage.setLoggedInUser(isLoggedInUser); - - sPreferences.edit().putLong(Constants.PrefKeys.MPID, newMpid).apply(); - if (mUserStorage == null || mUserStorage.getMpid() != newMpid) { - mUserStorage = userStorage; - mUserStorage.setFirstSeenTime(System.currentTimeMillis()); - } - if ((previousMpid != newMpid || currentLoggedInUser != isLoggedInUser)) { - triggerMpidChangeListenerCallbacks(newMpid, previousMpid); - } - } - - //for testing - static void clearMpid(Context context) { - if (sPreferences == null) { - sPreferences = context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE); - } - sPreferences.edit().remove(Constants.PrefKeys.MPID).apply(); - } - - public long getMpid() { - return getMpid(false); - } - - public long getMpid(boolean allowTemporary) { - if (allowTemporary && sInProgress) { - return Constants.TEMPORARY_MPID; - } else { - return sPreferences.getLong(Constants.PrefKeys.MPID, Constants.TEMPORARY_MPID); - } - } - - public static long getMpid(Context context) { - return getMpid(context, false); - } - - public static long getMpid(Context context, boolean allowTemporary) { - if (sPreferences == null) { - sPreferences = context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE); - } - if (allowTemporary && sInProgress) { - return Constants.TEMPORARY_MPID; - } else { - return sPreferences.getLong(Constants.PrefKeys.MPID, Constants.TEMPORARY_MPID); - } - } - - public boolean mpidExists(long mpid) { - return UserStorage.getMpIdSet(mContext).contains(mpid); - } - - public Set getMpids() { - return UserStorage.getMpIdSet(mContext); - } - - private static boolean sInProgress; - - public static void setIdentityRequestInProgress(boolean inProgress) { - sInProgress = inProgress; - } - - public void mergeUserConfigs(long subjectMpId, long targetMpId) { - UserStorage subjectUserStorage = getUserStorage(subjectMpId); - UserStorage targetUserStorage = getUserStorage(targetMpId); - targetUserStorage.merge(subjectUserStorage); - } - - public int getCurrentRampValue() { - return mRampValue; - } - - public JSONArray getTriggerMessageHashes() { - return mTriggerMessageHashes; - } - - public boolean shouldTrigger(BaseMPMessage message) { - JSONArray messageMatches = getTriggerMessageMatches(); - JSONArray triggerHashes = getTriggerMessageHashes(); - - boolean isBackgroundAst = false; - try { - isBackgroundAst = (message.getMessageType().equals(Constants.MessageType.APP_STATE_TRANSITION) && message.get(Constants.MessageKey.STATE_TRANSITION_TYPE).equals(Constants.StateTransitionType.STATE_TRANS_BG)); - } catch (JSONException ex) { - } - boolean shouldTrigger = message.getMessageType().equals(Constants.MessageType.PUSH_RECEIVED) - || message.getMessageType().equals(Constants.MessageType.COMMERCE_EVENT) - || isBackgroundAst; - - if (!shouldTrigger && messageMatches != null && messageMatches.length() > 0) { - shouldTrigger = true; - int i = 0; - while (shouldTrigger && i < messageMatches.length()) { - try { - JSONObject messageMatch = messageMatches.getJSONObject(i); - Iterator keys = messageMatch.keys(); - while (shouldTrigger && keys.hasNext()) { - String key = (String) keys.next(); - shouldTrigger = message.has(key); - if (shouldTrigger) { - try { - shouldTrigger = messageMatch.getString(key).equalsIgnoreCase(message.getString(key)); - } catch (JSONException stringex) { - try { - shouldTrigger = message.getBoolean(key) == messageMatch.getBoolean(key); - } catch (JSONException boolex) { - try { - shouldTrigger = message.getDouble(key) == messageMatch.getDouble(key); - } catch (JSONException doubleex) { - shouldTrigger = false; - } - } - } - } - } - } catch (Exception e) { - - } - i++; - } - } - if (!shouldTrigger && triggerHashes != null) { - for (int i = 0; i < triggerHashes.length(); i++) { - try { - if (triggerHashes.getInt(i) == message.getTypeNameHash()) { - shouldTrigger = true; - break; - } - } catch (JSONException jse) { - - } - } - } - return shouldTrigger; - } - - public int getUserBucket() { - if (mUserBucket < 0) { - mUserBucket = (int) (Math.abs(getMpid() >> 8) % 100); - } - return mUserBucket; - } - - public void setIntegrationAttributes(int integrationId, Map newAttributes) { - try { - JSONObject newJsonAttributes = null; - if (newAttributes != null && !newAttributes.isEmpty()) { - newJsonAttributes = new JSONObject(); - for (Map.Entry entry : newAttributes.entrySet()) { - newJsonAttributes.put(entry.getKey(), entry.getValue()); - } - } - JSONObject currentJsonAttributes = getIntegrationAttributes(); - if (currentJsonAttributes == null) { - currentJsonAttributes = new JSONObject(); - } - currentJsonAttributes.put(Integer.toString(integrationId), newJsonAttributes); - if (currentJsonAttributes.length() > 0) { - sPreferences.edit() - .putString(Constants.PrefKeys.INTEGRATION_ATTRIBUTES, currentJsonAttributes.toString()) - .apply(); - } else { - sPreferences.edit() - .remove(Constants.PrefKeys.INTEGRATION_ATTRIBUTES) - .apply(); - } - } catch (JSONException jse) { - - } - } - - @NonNull - public Map getIntegrationAttributes(int integrationId) { - Map integrationAttributes = new HashMap(); - JSONObject jsonAttributes = getIntegrationAttributes(); - if (jsonAttributes != null) { - JSONObject kitAttributes = jsonAttributes.optJSONObject(Integer.toString(integrationId)); - if (kitAttributes != null) { - try { - Iterator keys = kitAttributes.keys(); - while (keys.hasNext()) { - String key = keys.next(); - if (kitAttributes.get(key) instanceof String) { - integrationAttributes.put(key, kitAttributes.getString(key)); - } - } - } catch (JSONException e) { - - } - } - } - return integrationAttributes; - } - - public JSONObject getIntegrationAttributes() { - JSONObject jsonAttributes = null; - String allAttributes = sPreferences.getString(Constants.PrefKeys.INTEGRATION_ATTRIBUTES, null); - if (allAttributes != null) { - try { - jsonAttributes = new JSONObject(allAttributes); - } catch (JSONException e) { - - } - } - return jsonAttributes; - } - - public Map getUserIdentities(long mpId) { - JSONArray userIdentitiesJson = getUserIdentityJson(mpId); - Map identityTypeStringMap = new HashMap(userIdentitiesJson.length()); - - for (int i = 0; i < userIdentitiesJson.length(); i++) { - try { - JSONObject identity = userIdentitiesJson.getJSONObject(i); - identityTypeStringMap.put( - MParticle.IdentityType.parseInt(identity.getInt(Constants.MessageKey.IDENTITY_NAME)), - identity.getString(Constants.MessageKey.IDENTITY_VALUE) - ); - } catch (JSONException jse) { - - } - } - - return identityTypeStringMap; - } - - public JSONArray getUserIdentityJson() { - return getUserIdentityJson(getMpid()); - } - - @NonNull - public JSONArray getUserIdentityJson(long mpId) { - JSONArray userIdentities = null; - String userIds = getUserStorage(mpId).getUserIdentities(); - - try { - userIdentities = new JSONArray(userIds); - boolean changeMade = fixUpUserIdentities(userIdentities); - if (changeMade) { - saveUserIdentityJson(userIdentities, mpId); - } - } catch (Exception e) { - userIdentities = new JSONArray(); - } - return userIdentities; - } - - public void saveUserIdentityJson(JSONArray userIdentities) { - saveUserIdentityJson(userIdentities, getMpid()); - } - - public void saveUserIdentityJson(JSONArray userIdentities, long mpId) { - getUserStorage(mpId).setUserIdentities(userIdentities.toString()); - } - - public String getDataplanId() { - return mDataplanId; - } - - public Integer getDataplanVersion() { - return mDataplanVersion; - } - - private static boolean fixUpUserIdentities(JSONArray identities) { - boolean changeMade = false; - try { - for (int i = 0; i < identities.length(); i++) { - JSONObject identity = identities.getJSONObject(i); - if (!identity.has(Constants.MessageKey.IDENTITY_DATE_FIRST_SEEN)) { - identity.put(Constants.MessageKey.IDENTITY_DATE_FIRST_SEEN, 0); - changeMade = true; - } - if (!identity.has(Constants.MessageKey.IDENTITY_FIRST_SEEN)) { - identity.put(Constants.MessageKey.IDENTITY_FIRST_SEEN, true); - changeMade = true; - } - } - - } catch (JSONException jse) { - - } - return changeMade; - } - - public String getDeviceApplicationStamp() { - String das = sPreferences.getString(Constants.PrefKeys.DEVICE_APPLICATION_STAMP, null); - if (MPUtility.isEmpty(das)) { - das = UUID.randomUUID().toString(); - sPreferences.edit() - .putString(Constants.PrefKeys.DEVICE_APPLICATION_STAMP, das) - .apply(); - } - return das; - } - - public JSONObject getCookies(long mpId) { - if (mCurrentCookies == null) { - String currentCookies = getUserStorage(mpId).getCookies(); - if (MPUtility.isEmpty(currentCookies)) { - mCurrentCookies = new JSONObject(); - getUserStorage(mpId).setCookies(mCurrentCookies.toString()); - return mCurrentCookies; - } else { - try { - mCurrentCookies = new JSONObject(currentCookies); - } catch (JSONException e) { - mCurrentCookies = new JSONObject(); - } - } - Calendar nowCalendar = Calendar.getInstance(); - nowCalendar.set(Calendar.YEAR, 1990); - Date oldDate = nowCalendar.getTime(); - SimpleDateFormat parser = new SimpleDateFormat("yyyy"); - Iterator keys = mCurrentCookies.keys(); - ArrayList keysToRemove = new ArrayList(); - while (keys.hasNext()) { - try { - String key = (String) keys.next(); - if (mCurrentCookies.get(key) instanceof JSONObject) { - String expiration = ((JSONObject) mCurrentCookies.get(key)).getString("e"); - try { - Date date = parser.parse(expiration); - if (date.before(oldDate)) { - keysToRemove.add(key); - } - } catch (ParseException dpe) { - - } - } - } catch (JSONException jse) { - - } - } - for (String key : keysToRemove) { - mCurrentCookies.remove(key); - } - if (keysToRemove.size() > 0) { - getUserStorage(mpId).setCookies(mCurrentCookies.toString()); - } - return mCurrentCookies; - } else { - return mCurrentCookies; - } - } - - JSONArray markIdentitiesAsSeen(JSONArray uploadedIdentities) { - return markIdentitiesAsSeen(uploadedIdentities, getMpid()); - } - - JSONArray markIdentitiesAsSeen(JSONArray uploadedIdentities, long mpId) { - try { - - JSONArray currentIdentities = getUserIdentityJson(mpId); - if (currentIdentities.length() == 0) { - return null; - } - uploadedIdentities = new JSONArray(uploadedIdentities.toString()); - Set identityTypes = new HashSet(); - for (int i = 0; i < uploadedIdentities.length(); i++) { - if (uploadedIdentities.getJSONObject(i).optBoolean(Constants.MessageKey.IDENTITY_FIRST_SEEN)) { - identityTypes.add(uploadedIdentities.getJSONObject(i).getInt(Constants.MessageKey.IDENTITY_NAME)); - } - } - if (identityTypes.size() > 0) { - for (int i = 0; i < currentIdentities.length(); i++) { - int identity = currentIdentities.getJSONObject(i).getInt(Constants.MessageKey.IDENTITY_NAME); - if (identityTypes.contains(identity)) { - currentIdentities.getJSONObject(i).put(Constants.MessageKey.IDENTITY_FIRST_SEEN, false); - } - } - return currentIdentities; - } - } catch (JSONException jse) { - - } - return null; - } - - public String getIdentityApiContext() { - return sPreferences.getString(Constants.PrefKeys.IDENTITY_API_CONTEXT, null); - } - - public void setIdentityApiContext(String context) { - sPreferences.edit().putString(Constants.PrefKeys.IDENTITY_API_CONTEXT, context).apply(); - } - - public String getPreviousAdId() { - MPUtility.AdIdInfo adInfo = MPUtility.getAdIdInfo(mContext); - if (adInfo != null && !adInfo.isLimitAdTrackingEnabled) { - return sPreferences.getString(Constants.PrefKeys.PREVIOUS_ANDROID_ID, null); - } - return null; - } - - public void setPreviousAdId() { - MPUtility.AdIdInfo adInfo = MPUtility.getAdIdInfo(mContext); - if (adInfo != null && !adInfo.isLimitAdTrackingEnabled) { - sPreferences.edit().putString(Constants.PrefKeys.PREVIOUS_ANDROID_ID, adInfo.id).apply(); - } else { - sPreferences.edit().remove(Constants.PrefKeys.PREVIOUS_ANDROID_ID).apply(); - } - } - - public int getIdentityConnectionTimeout() { - return sPreferences.getInt(Constants.PrefKeys.IDENTITY_CONNECTION_TIMEOUT, DEFAULT_CONNECTION_TIMEOUT_SECONDS) * 1000; - } - - public int getConnectionTimeout() { - return DEFAULT_CONNECTION_TIMEOUT_SECONDS * 1000; - } - - public void setIdentityConnectionTimeout(int connectionTimeout) { - if (connectionTimeout >= MINIMUM_CONNECTION_TIMEOUT_SECONDS) { - sPreferences.edit().putInt(Constants.PrefKeys.IDENTITY_CONNECTION_TIMEOUT, connectionTimeout).apply(); - } - } - - private static Set mpIdChangeListeners = new HashSet(); - - public static void addMpIdChangeListener(IdentityApi.MpIdChangeListener listener) { - mpIdChangeListeners.add(listener); - } - - private void triggerMpidChangeListenerCallbacks(long mpid, long previousMpid) { - if (MPUtility.isEmpty(mpIdChangeListeners)) { - return; - } - for (IdentityApi.MpIdChangeListener listenerRef : new ArrayList(mpIdChangeListeners)) { - if (listenerRef != null) { - listenerRef.onMpIdChanged(mpid, previousMpid); - } - } - } - - public ConsentState getConsentState(long mpid) { - String serializedConsent = getUserStorage(mpid).getSerializedConsentState(); - return ConsentState.withConsentState(serializedConsent).build(); - } - - public boolean isDirectUrlRoutingEnabled() { - return directUrlRouting; - } - - - /* This function is called to get the specific pod/silo prefix when the `directUrlRouting` is `true`. mParticle API keys are prefixed with the - silo and a hyphen (ex. "us1-", "us2-", "eu1-"). us1 was the first silo,and before other silos existed, there were no prefixes and all apiKeys - were us1. As such, if we split on a '-' and the resulting array length is 1, then it is an older APIkey that should route to us1. - When splitKey.length is greater than 1, then splitKey[0] will be us1, us2, eu1, au1, or st1, etc as new silos are added */ - public String getPodPrefix() { - String prefix = "us1"; - try { - String[] prefixFromApi = getApiKey().split("-"); - if (prefixFromApi.length > 1) { - prefix = prefixFromApi[0]; - } - } catch (Exception e) { - prefix = "us1"; - Logger.error("Error while getting pod prefix for direct URL routing : " + e); - } - return prefix; - } - - public void setConsentState(ConsentState state, long mpid) { - String serializedConsent = null; - if (state != null) { - serializedConsent = state.toString(); - } - getUserStorage(mpid).setSerializedConsentState(serializedConsent); - } - - public NetworkOptions getNetworkOptions() { - if (sNetworkOptions == null) { - sNetworkOptions = NetworkOptionsManager.validateAndResolve(null); - } - return sNetworkOptions; - } - - public synchronized void setNetworkOptions(NetworkOptions networkOptions) { - sNetworkOptions = networkOptions; - sPreferences.edit().remove(Constants.PrefKeys.NETWORK_OPTIONS).apply(); - } - - @NonNull - public String getWorkspaceToken() { - return sPreferences.getString(WORKSPACE_TOKEN, ""); - } - - /** - * the maximum allowed age of "start_time" in an AliasRequest, in days - * - * @return - */ - public int getAliasMaxWindow() { - return sPreferences.getInt(ALIAS_MAX_WINDOW, DEFAULT_MAX_ALIAS_WINDOW_DAYS); - } - - public String getEtag() { - return sPreferences.getString(Constants.PrefKeys.ETAG, null); - } - - public String getIfModified() { - return sPreferences.getString(Constants.PrefKeys.IF_MODIFIED, null); - } - - public void addConfigUpdatedListener(ConfigLoadedListener listener) { - configUpdatedListeners.add(listener); - } - - private void onConfigLoaded(ConfigType configType, Boolean isNew) { - Logger.debug("Loading " + (isNew ? "new " : "cached ") + configType.name().toLowerCase(Locale.ROOT) + " config"); - for (ConfigLoadedListener listener : new ArrayList(configUpdatedListeners)) { - if (listener != null) { - listener.onConfigUpdated(configType, isNew); - } - } - } - - @Nullable - MParticleOptions.DataplanOptions parseDataplanOptions(JSONObject jsonObject) { - if (jsonObject != null) { - JSONObject dataplanConfig = jsonObject.optJSONObject(DATAPLAN_KEY); - if (dataplanConfig != null) { - JSONObject dataplanContanier = dataplanConfig.optJSONObject(DATAPLAN_OBJ); - if (dataplanContanier != null) { - JSONObject block = dataplanContanier.optJSONObject(DATAPLAN_BLOCKING); - JSONObject dataplanVersion = dataplanContanier.optJSONObject(DATAPLAN_VERSION); - if (block != null) { - return MParticleOptions.DataplanOptions.builder() - .dataplanVersion(dataplanVersion) - .blockEvents(block.optBoolean(DATAPLAN_BLOCK_EVENTS, false)) - .blockEventAttributes(block.optBoolean(DATAPLAN_BLOCK_EVENT_ATTRIBUTES, false)) - .blockUserAttributes(block.optBoolean(DATAPLAN_BLOCK_USER_ATTRIBUTES, false)) - .blockUserIdentities(block.optBoolean(DATAPLAN_BLOCK_USER_IDENTITIES, false)) - .build(); - } - } - } - } - return null; - } - - private int getAppVersion() { - try { - PackageInfo packageInfo = mContext.getPackageManager() - .getPackageInfo(mContext.getPackageName(), 0); - return packageInfo.versionCode; - } catch (PackageManager.NameNotFoundException e) { - // should never happen - throw new RuntimeException("Could not get package name: " + e); - } - } - - public enum ConfigType { - CORE, - KIT - } - - public interface ConfigLoadedListener { - public void onConfigUpdated(ConfigType configType, boolean isNew); - } -} \ No newline at end of file diff --git a/android-core/src/main/kotlin/com/mparticle/internal/ConfigManager.kt b/android-core/src/main/kotlin/com/mparticle/internal/ConfigManager.kt new file mode 100644 index 000000000..719b07ad1 --- /dev/null +++ b/android-core/src/main/kotlin/com/mparticle/internal/ConfigManager.kt @@ -0,0 +1,1436 @@ +package com.mparticle.internal + +import android.content.Context +import android.content.SharedPreferences +import android.content.pm.PackageManager +import android.os.Build +import androidx.annotation.WorkerThread +import com.mparticle.Configuration +import com.mparticle.ExceptionHandler +import com.mparticle.MParticle +import com.mparticle.MParticle.IdentityType +import com.mparticle.MParticleOptions +import com.mparticle.MParticleOptions.DataplanOptions +import com.mparticle.consent.ConsentState +import com.mparticle.identity.IdentityApi.MpIdChangeListener +import com.mparticle.internal.KitManager.KitStatus +import com.mparticle.internal.MPUtility.AdIdInfo +import com.mparticle.internal.MPUtility.getAdIdInfo +import com.mparticle.internal.MPUtility.isEmpty +import com.mparticle.internal.PushRegistrationHelper.PushRegistration +import com.mparticle.internal.SideloadedKitsUtils.combineConfig +import com.mparticle.internal.UserStorage.Companion.create +import com.mparticle.internal.UserStorage.Companion.getMpIdSet +import com.mparticle.internal.messages.BaseMPMessage +import com.mparticle.networking.NetworkOptions +import com.mparticle.networking.NetworkOptionsManager +import org.json.JSONArray +import org.json.JSONException +import org.json.JSONObject +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.Calendar +import java.util.Collections +import java.util.Date +import java.util.UUID +import java.util.concurrent.TimeUnit +import kotlin.math.abs + +open class ConfigManager { + private var mContext: Context? = null + private var mIgnoreDataplanOptionsFromConfig: Boolean = false + var dataplanOptions: DataplanOptions? = null + private set + + var isDirectUrlRoutingEnabled: Boolean = false + private set + private var mUserStorage: UserStorage? = null + private var mLogUnhandledExceptions: String = VALUE_APP_DEFINED + + private var mSendOoEvents: Boolean = false + + @get:Synchronized + @set:Synchronized + var providerPersistence: JSONObject? = null + private set + var currentRampValue: Int = -1 + private set + private var mUserBucket: Int = -1 + + private var mSessionTimeoutInterval: Int = -1 + private var mUploadInterval: Int = -1 + var influenceOpenTimeoutMillis: Long = (3600 * 1000).toLong() + private set + var triggerMessageMatches: JSONArray? = null + private set + var triggerMessageHashes: JSONArray? = null + private set + private var mExHandler: ExceptionHandler? = null + private var mCurrentCookies: JSONObject? = null + var dataplanId: String? = null + private set + var dataplanVersion: Int? = null + private set + private var mMaxConfigAge: Int? = null + private val configUpdatedListeners: MutableList = ArrayList() + private var sideloadedKits: List = ArrayList() + + private constructor() : super() + + constructor(context: Context) { + mContext = context + mContext?.let { sPreferences = getPreferences(it) } + } + + constructor(options: MParticleOptions) : this( + options.context, + options.environment, + options.apiKey, + options.apiSecret, + options.dataplanOptions, + options.dataplanId, + options.dataplanVersion, + options.configMaxAge, + options.getConfigurationsForTarget( + ConfigManager::class.java + ), + options.sideloadedKits + ) + + constructor( + context: Context, + environment: MParticle.Environment?, + apiKey: String?, + apiSecret: String?, + dataplanOptions: DataplanOptions?, + dataplanId: String?, + dataplanVersion: Int?, + configMaxAge: Int?, + configurations: List>?, + sideloadedKits: List? + ) { + mContext = context.applicationContext + mContext?.let { sPreferences = getPreferences(it) } + if (apiKey != null || apiSecret != null) { + setCredentials(apiKey, apiSecret) + } + if (environment != null) { + setEnvironment(environment) + } + mContext?.let { mUserStorage = create(it, mpid) } + // if we are initialized with a DataplanOptions instance, then we will ignore values from remote config + mIgnoreDataplanOptionsFromConfig = dataplanOptions != null + this.dataplanOptions = dataplanOptions + this.dataplanVersion = dataplanVersion + this.dataplanId = dataplanId + mMaxConfigAge = configMaxAge + if (sideloadedKits != null) { + this.sideloadedKits = sideloadedKits + } else { + this.sideloadedKits = ArrayList() + } + configurations?.let { + for (configuration in configurations) { + configuration.apply(this) + } + } + } + + fun onMParticleStarted() { + checkConfigStaleness() + migrateConfigIfNeeded() + restoreCoreConfig() + } + + private fun restoreCoreConfig() { + val oldConfig: String? = config + if (!isEmpty(oldConfig)) { + try { + val oldConfigJson: JSONObject = JSONObject(oldConfig) + reloadCoreConfig(oldConfigJson) + } catch (jse: Exception) { + } + } + } + + @get:WorkerThread + open val latestKitConfiguration: JSONArray? + /** + * This called on startup. The only thing that's completely necessary is that we fire up kits. + */ + get() { + val oldConfig: String? = kitConfigPreferences?.getString(KIT_CONFIG_KEY, null) + if (!isEmpty(oldConfig)) { + try { + return JSONArray(oldConfig) + } catch (jse: Exception) { + } + } + return null + } + + open val userStorage: UserStorage? + get() = getUserStorage(mpid) + + fun getUserStorage(mpId: Long): UserStorage? { + if (mUserStorage == null || mUserStorage?.mpid != mpId) { + mUserStorage = mContext?.let { create(it, mpId) } + } + return mUserStorage + } + + fun deleteUserStorage(context: Context, mpid: Long) { + if (mUserStorage != null) { + mUserStorage?.deleteUserConfig(context, mpid) + } + } + + fun deleteUserStorage(mpId: Long) { + mContext?.let { deleteUserStorage(it, mpId) } + } + + fun migrateConfigIfNeeded() { + if (sPreferences?.getBoolean(MIGRATED_TO_KIT_SHARED_PREFS, false) == false) { + sPreferences?.edit()?.putBoolean(MIGRATED_TO_KIT_SHARED_PREFS, true)?.apply() + val configString: String? = sPreferences?.getString(CONFIG_JSON, null) + if (!isEmpty(configString)) { + try { + // save ourselves some time and only parse the JSONObject if might contain the embedded kits key + if (configString?.contains("\"" + KEY_EMBEDDED_KITS + "\":") == true) { + Logger.info("Migrating kit configuration") + saveConfigJson(JSONObject(configString), etag, ifModified, configTimestamp) + } + } catch (jse: JSONException) { + } + } + } + } + + /** + * detrmine if the stored config age is greater than mMaxConfigAge and clear it from storage if it is + */ + fun checkConfigStaleness() { + var storageDate: Long? = configTimestamp + if (storageDate == null) { + // migration step: if the current config does not have a timestamp, set one to the current time + configTimestamp = System.currentTimeMillis() + storageDate = configTimestamp + } + mMaxConfigAge?.let { + if (it < 0) { + return + } + } ?: return + if (mMaxConfigAge == 0) { + clearConfig() + } else { + val currentTime = System.currentTimeMillis() + val storageDateMillis = storageDate ?: 0L + if (currentTime >= storageDateMillis + TimeUnit.SECONDS.toMillis(mMaxConfigAge?.toLong() ?: 0L)) { + clearConfig() + } + } + } + + val config: String? + get() = sPreferences?.getString(CONFIG_JSON, "") + + var configTimestamp: Long? + get() { + if (sPreferences?.contains(CONFIG_JSON_TIMESTAMP) == true) { + return sPreferences?.getLong(CONFIG_JSON_TIMESTAMP, 0) + } else { + return null + } + } + set(timestamp) { + if (timestamp != null) { + sPreferences?.edit() + ?.putLong(CONFIG_JSON_TIMESTAMP, timestamp) + ?.apply() + } + } + + @JvmOverloads + @Throws(JSONException::class) + fun saveConfigJson(combinedConfig: JSONObject?, etag: String? = null, lastModified: String? = null, timestamp: Long? = null) { + if (combinedConfig != null) { + val kitConfig: JSONArray? = if (combinedConfig.has(KEY_EMBEDDED_KITS)) combinedConfig.remove(KEY_EMBEDDED_KITS) as JSONArray else null + saveConfigJson(combinedConfig, kitConfig, etag, lastModified, timestamp) + } else { + saveConfigJson(combinedConfig, null, etag, lastModified, timestamp) + } + } + + @Throws(JSONException::class) + fun saveConfigJson(coreConfig: JSONObject?, kitConfig: JSONArray?, etag: String?, lastModified: String?, timestamp: Long?) { + if (coreConfig != null) { + val kitConfigString: String? = if (kitConfig != null) kitConfig.toString() else null + Logger.debug("Updating core config to:\n$coreConfig") + Logger.debug("Updating kit config to:\n$kitConfigString") + sPreferences?.edit() + ?.putString(CONFIG_JSON, coreConfig.toString()) + ?.putLong(CONFIG_JSON_TIMESTAMP, if (timestamp != null) timestamp else System.currentTimeMillis()) + ?.putString(Constants.PrefKeys.ETAG, etag) + ?.putString(Constants.PrefKeys.IF_MODIFIED, lastModified) + ?.apply() + kitConfigPreferences + ?.edit() + ?.putString(KIT_CONFIG_KEY, combineConfig(kitConfig, sideloadedKits).toString()) + ?.apply() + } else { + Logger.debug("clearing current configurations") + clearConfig() + } + } + + fun clearConfig() { + sPreferences?.edit() + ?.remove(CONFIG_JSON) + ?.remove(CONFIG_JSON_TIMESTAMP) + ?.remove(Constants.PrefKeys.ETAG) + ?.remove(Constants.PrefKeys.IF_MODIFIED) + ?.apply() + kitConfigPreferences + ?.edit() + ?.remove(KIT_CONFIG_KEY) + ?.apply() + } + + @Synchronized + @Throws(JSONException::class) + fun updateConfig(responseJSON: JSONObject?) { + updateConfig(responseJSON, null, null) + } + + @Synchronized + @Throws(JSONException::class) + fun configUpToDate() { + try { + val config: String? = kitConfigPreferences?.getString(KIT_CONFIG_KEY, "") + if (!config.isNullOrEmpty()) { + val kitConfig: JSONArray = JSONArray(config) + val combined: JSONArray = combineConfig(kitConfig, sideloadedKits) + kitConfigPreferences + ?.edit() + ?.putString(KIT_CONFIG_KEY, combined.toString()) + ?.apply() + onConfigLoaded(ConfigType.KIT, kitConfig !== combined) + } + } catch (e: JSONException) { + e.printStackTrace() + throw RuntimeException(e) + } + } + + @Synchronized + @Throws(JSONException::class) + open fun updateConfig(responseJSON: JSONObject?, etag: String?, lastModified: String?) { + var responseJSON: JSONObject? = responseJSON + if (responseJSON == null) { + responseJSON = JSONObject() + } + val kitConfig: JSONArray? = if (responseJSON.has(KEY_EMBEDDED_KITS)) responseJSON.remove(KEY_EMBEDDED_KITS) as JSONArray else null + saveConfigJson(responseJSON, kitConfig, etag, lastModified, System.currentTimeMillis()) + updateCoreConfig(responseJSON, true) + updateKitConfig(kitConfig) + } + + @Synchronized + @Throws(JSONException::class) + fun reloadCoreConfig(responseJSON: JSONObject) { + updateCoreConfig(responseJSON, false) + } + + @Synchronized + private fun updateKitConfig(kitConfigs: JSONArray?) { + val instance: MParticle? = MParticle.getInstance() + instance?.let { + instance.Internal().kitManager + .updateKits(kitConfigs) + .onKitsLoaded(object : OnKitManagerLoaded { + override fun onKitManagerLoaded() { + onConfigLoaded(ConfigType.KIT, true) + } + }) + } + } + + @Synchronized + @Throws(JSONException::class) + private fun updateCoreConfig(responseJSON: JSONObject, newConfig: Boolean) { + val editor: SharedPreferences.Editor? = sPreferences?.edit() + if (responseJSON.has(KEY_UNHANDLED_EXCEPTIONS)) { + mLogUnhandledExceptions = responseJSON.getString(KEY_UNHANDLED_EXCEPTIONS) + } + + if (responseJSON.has(KEY_PUSH_MESSAGES) && newConfig) { + sPushKeys = responseJSON.getJSONArray(KEY_PUSH_MESSAGES) + editor?.putString(KEY_PUSH_MESSAGES, sPushKeys.toString()) + } + + if (responseJSON.has(KEY_DIRECT_URL_ROUTING)) { + isDirectUrlRoutingEnabled = responseJSON.optBoolean(KEY_DIRECT_URL_ROUTING) + editor?.putBoolean(KEY_DIRECT_URL_ROUTING, isDirectUrlRoutingEnabled) + } + + currentRampValue = responseJSON.optInt(KEY_RAMP, -1) + + if (responseJSON.has(KEY_OPT_OUT)) { + mSendOoEvents = responseJSON.getBoolean(KEY_OPT_OUT) + } else { + mSendOoEvents = false + } + + if (responseJSON.has(ProviderPersistence.KEY_PERSISTENCE)) { + providerPersistence = ProviderPersistence(responseJSON, mContext) + } else { + providerPersistence = null + } + + mSessionTimeoutInterval = responseJSON.optInt(KEY_SESSION_TIMEOUT, -1) + mUploadInterval = responseJSON.optInt(KEY_UPLOAD_INTERVAL, -1) + + triggerMessageMatches = null + triggerMessageHashes = null + if (responseJSON.has(KEY_TRIGGER_ITEMS)) { + try { + val items: JSONObject = responseJSON.getJSONObject(KEY_TRIGGER_ITEMS) + if (items.has(KEY_MESSAGE_MATCHES)) { + triggerMessageMatches = items.getJSONArray(KEY_MESSAGE_MATCHES) + } + if (items.has(KEY_TRIGGER_ITEM_HASHES)) { + triggerMessageHashes = items.getJSONArray(KEY_TRIGGER_ITEM_HASHES) + } + } catch (jse: JSONException) { + } + } + + if (responseJSON.has(KEY_INFLUENCE_OPEN)) { + influenceOpenTimeoutMillis = responseJSON.getLong(KEY_INFLUENCE_OPEN) * 60 * 1000 + } else { + influenceOpenTimeoutMillis = (30 * 60 * 1000).toLong() + } + + if (responseJSON.has(KEY_DEVICE_PERFORMANCE_METRICS_DISABLED)) { + MessageManager.devicePerformanceMetricsDisabled = responseJSON.optBoolean(KEY_DEVICE_PERFORMANCE_METRICS_DISABLED, false) + } + if (responseJSON.has(WORKSPACE_TOKEN)) { + editor?.putString(WORKSPACE_TOKEN, responseJSON.getString(WORKSPACE_TOKEN)) + } else { + editor?.remove(WORKSPACE_TOKEN) + } + if (responseJSON.has(ALIAS_MAX_WINDOW)) { + editor?.putInt(ALIAS_MAX_WINDOW, responseJSON.getInt(ALIAS_MAX_WINDOW)) + } else { + editor?.remove(ALIAS_MAX_WINDOW) + } + if (!mIgnoreDataplanOptionsFromConfig) { + dataplanOptions = parseDataplanOptions(responseJSON) + val instance: MParticle? = MParticle.getInstance() + if (instance != null) { + instance.Internal().kitManager.updateDataplan(dataplanOptions) + } + } + editor?.apply() + applyConfig() + onConfigLoaded(ConfigType.CORE, newConfig) + } + + val activeModuleIds: String + get() { + val kitStatusMap: MutableMap? = MParticle.getInstance()?.Internal()?.kitManager?.kitStatus + val activeKits: MutableList = ArrayList() + kitStatusMap?.let { + for (kitStatus: Map.Entry in kitStatusMap.entries) { + val status: KitStatus = kitStatus.value + when (status) { + KitStatus.ACTIVE, KitStatus.STOPPED -> activeKits.add(kitStatus.key) + else -> { + } + } + } + } + Collections.sort(activeKits) + if (activeKits.size == 0) { + return "" + } else { + val builder: StringBuilder = StringBuilder(activeKits.size * 3) + for (kitId: Int? in activeKits) { + builder.append(kitId) + builder.append(",") + } + builder.deleteCharAt(builder.length - 1) + return builder.toString() + } + } + + /** + * When the Config manager starts up, we don't want to enable everything immediately to save on app-load time. + * This method will be called from a background thread after startup is already complete. + */ + fun delayedStart() { + val senderId: String? = pushSenderId + if (isPushEnabled && senderId != null) { + MParticle.getInstance()?.Messaging()?.enablePushNotifications(senderId) + } + } + + private fun applyConfig() { + if (logUnhandledExceptions) { + enableUncaughtExceptionLogging(false) + } else { + disableUncaughtExceptionLogging(false) + } + } + + fun enableUncaughtExceptionLogging(userTriggered: Boolean) { + if (userTriggered) { + logUnhandledExceptions = true + } + if (null == mExHandler) { + val currentUncaughtExceptionHandler: Thread.UncaughtExceptionHandler? = Thread.getDefaultUncaughtExceptionHandler() + if (currentUncaughtExceptionHandler !is ExceptionHandler) { + mExHandler = ExceptionHandler(currentUncaughtExceptionHandler) + Thread.setDefaultUncaughtExceptionHandler(mExHandler) + } + } + } + + fun disableUncaughtExceptionLogging(userTriggered: Boolean) { + if (userTriggered) { + logUnhandledExceptions = false + } + if (null != mExHandler) { + val currentUncaughtExceptionHandler: Thread.UncaughtExceptionHandler? = Thread.getDefaultUncaughtExceptionHandler() + if (currentUncaughtExceptionHandler is ExceptionHandler) { + Thread.setDefaultUncaughtExceptionHandler(mExHandler?.originalExceptionHandler) + mExHandler = null + } + } + } + + var logUnhandledExceptions: Boolean + get() { + if (VALUE_APP_DEFINED == mLogUnhandledExceptions) { + return sPreferences?.getBoolean(Constants.PrefKeys.REPORT_UNCAUGHT_EXCEPTIONS, false) == true + } else { + return VALUE_CUE_CATCH == mLogUnhandledExceptions + } + } + set(log) { + sPreferences?.edit()?.putBoolean(Constants.PrefKeys.REPORT_UNCAUGHT_EXCEPTIONS, log)?.apply() + } + open val apiKey: String + get() = sPreferences?.getString(Constants.PrefKeys.API_KEY, null) ?: "" + + open val apiSecret: String + get() = sPreferences?.getString(Constants.PrefKeys.API_SECRET, null) ?: "" + + fun setCredentials(apiKey: String?, secret: String?) { + sPreferences?.edit() + ?.putString(Constants.PrefKeys.API_KEY, apiKey) + ?.putString(Constants.PrefKeys.API_SECRET, secret) + ?.apply() + } + + open val uploadInterval: Long + get() { + return if (getEnvironment() == MParticle.Environment.Development) { + DEVMODE_UPLOAD_INTERVAL_MILLISECONDS.toLong() + } else { + if (mUploadInterval > 0) { + (1000 * mUploadInterval).toLong() + } else { + ((1000 * (sPreferences?.getInt(Constants.PrefKeys.UPLOAD_INTERVAL, DEFAULT_UPLOAD_INTERVAL) ?: DEFAULT_UPLOAD_INTERVAL)).toLong()) + } + } + } + + fun setEnvironment(environment: MParticle.Environment?) { + if (environment != null) { + sPreferences?.edit()?.putInt(Constants.PrefKeys.ENVIRONMENT, environment.value)?.apply() + } else { + sPreferences?.edit()?.remove(Constants.PrefKeys.ENVIRONMENT)?.apply() + } + } + + fun setUploadInterval(uploadInterval: Int) { + sPreferences?.edit()?.putInt(Constants.PrefKeys.UPLOAD_INTERVAL, uploadInterval)?.apply() + } + + var sessionTimeout: Int + get() { + if (mSessionTimeoutInterval > 0) { + return mSessionTimeoutInterval * 1000 + } else { + return sPreferences?.getInt( + Constants.PrefKeys.SESSION_TIMEOUT, + DEFAULT_SESSION_TIMEOUT_SECONDS + )?.times(1000) ?: 0 + } + } + set(sessionTimeout) { + sPreferences?.edit()?.putInt(Constants.PrefKeys.SESSION_TIMEOUT, sessionTimeout)?.apply() + } + + open val isPushEnabled: Boolean + get() = sPreferences?.getBoolean( + Constants.PrefKeys.PUSH_ENABLED, + false + ) == true && pushSenderId != null + + open var pushSenderId: String? + get() { + val pushRegistration: PushRegistration? = pushRegistration + if (pushRegistration != null) { + return pushRegistration.senderId + } else { + return null + } + } + set(senderId) { + sPreferences?.edit()?.putString(Constants.PrefKeys.PUSH_SENDER_ID, senderId) + ?.putBoolean(Constants.PrefKeys.PUSH_ENABLED, true) + ?.apply() + } + + open var pushInstanceId: String? + get() { + val pushRegistration: PushRegistration? = pushRegistration + if (pushRegistration != null) { + return pushRegistration.instanceId + } else { + return null + } + } + set(token) { + sPreferences?.edit()?.putString(Constants.PrefKeys.PUSH_INSTANCE_ID, token)?.apply() + } + + open var pushRegistration: PushRegistration? + get() { + val senderId: String? = + sPreferences?.getString(Constants.PrefKeys.PUSH_SENDER_ID, null) + val instanceId: String? = + sPreferences?.getString(Constants.PrefKeys.PUSH_INSTANCE_ID, null) + return PushRegistration(instanceId, senderId) + } + set(pushRegistration) { + if (pushRegistration == null || isEmpty(pushRegistration.senderId)) { + clearPushRegistration() + } else { + pushSenderId = pushRegistration.senderId + pushInstanceId = pushRegistration.instanceId + } + } + + fun setPushRegistrationFetched() { + val appVersion: Int = appVersion + sPreferences?.edit() + ?.putInt(Constants.PrefKeys.PROPERTY_APP_VERSION, appVersion) + ?.putInt(Constants.PrefKeys.PROPERTY_OS_VERSION, Build.VERSION.SDK_INT) + ?.apply() + } + + val isPushRegistrationFetched: Boolean + get() { + // Check if app was updated; if so, it must clear the registration ID + // since the existing regID is not guaranteed to work with the new + // app version. + val registeredVersion: Int = sPreferences?.getInt( + Constants.PrefKeys.PROPERTY_APP_VERSION, + Int.MIN_VALUE + ) ?: Int.MIN_VALUE + val currentVersion: Int = appVersion + val osVersion: Int = sPreferences?.getInt( + Constants.PrefKeys.PROPERTY_OS_VERSION, + Int.MIN_VALUE + ) ?: Int.MIN_VALUE + return registeredVersion == currentVersion && osVersion == Build.VERSION.SDK_INT + } + + val pushInstanceIdBackground: String? + get() { + return sPreferences?.getString(Constants.PrefKeys.PUSH_INSTANCE_ID_BACKGROUND, null) + } + + fun setPushRegistrationInBackground(pushRegistration: PushRegistration?) { + var oldInstanceId: String? = pushInstanceId + if (oldInstanceId == null) { + oldInstanceId = "" + } + sPreferences?.edit() + ?.putString(Constants.PrefKeys.PUSH_INSTANCE_ID_BACKGROUND, oldInstanceId) + ?.apply() + this.pushRegistration = pushRegistration + } + + fun clearPushRegistration() { + sPreferences?.edit() + ?.remove(Constants.PrefKeys.PUSH_SENDER_ID) + ?.remove(Constants.PrefKeys.PUSH_INSTANCE_ID) + ?.remove(Constants.PrefKeys.PUSH_ENABLED) + ?.remove(Constants.PrefKeys.PROPERTY_APP_VERSION) + ?.remove(Constants.PrefKeys.PROPERTY_OS_VERSION) + ?.apply() + } + + fun clearPushRegistrationBackground() { + sPreferences?.edit() + ?.remove(Constants.PrefKeys.PUSH_INSTANCE_ID_BACKGROUND) + ?.apply() + } + + open val isEnabled: Boolean + get() { + val optedOut: Boolean = this.optedOut + return !optedOut || mSendOoEvents + } + + fun setOptOut(optOut: Boolean) { + sPreferences?.edit()?.putBoolean(Constants.PrefKeys.OPTOUT, optOut)?.apply() + } + + val optedOut: Boolean + get() { + return sPreferences?.getBoolean(Constants.PrefKeys.OPTOUT, false) == true + } + + fun setPushNotificationIcon(pushNotificationIcon: Int) { + sPreferences?.edit() + ?.putInt(Constants.PrefKeys.PUSH_ICON, pushNotificationIcon) + ?.apply() + } + + fun setPushNotificationTitle(pushNotificationTitle: Int) { + sPreferences?.edit() + ?.putInt(Constants.PrefKeys.PUSH_TITLE, pushNotificationTitle) + ?.apply() + } + + fun setDisplayPushNotifications(display: Boolean) { + sPreferences?.edit() + ?.putBoolean(Constants.PrefKeys.DISPLAY_PUSH_NOTIFICATIONS, display) + ?.apply() + } + + val kitConfigPreferences: SharedPreferences? + get() { + return mContext?.getSharedPreferences(KIT_CONFIG_PREFERENCES, Context.MODE_PRIVATE) + } + + fun setBreadcrumbLimit(newLimit: Int) { + setBreadcrumbLimit(newLimit, mpid) + } + + fun setBreadcrumbLimit(newLimit: Int, mpId: Long) { + getUserStorage(mpId)?.breadcrumbLimit = newLimit + } + + fun setMpid(newMpid: Long, isLoggedInUser: Boolean) { + val previousMpid: Long = mpid + var currentLoggedInUser: Boolean = false + val currentUserStorage: UserStorage? = userStorage + if (currentUserStorage != null) { + currentUserStorage.lastSeenTime = System.currentTimeMillis() + currentLoggedInUser = currentUserStorage.isLoggedIn + } + val userStorage: UserStorage? = mContext?.let { create(it, newMpid) } + userStorage?.setLoggedInUser(isLoggedInUser) + + sPreferences?.edit()?.putLong(Constants.PrefKeys.MPID, newMpid)?.apply() + if (mUserStorage == null || mUserStorage?.mpid != newMpid) { + mUserStorage = userStorage + mUserStorage?.firstSeenTime = System.currentTimeMillis() + } + if ((previousMpid != newMpid || currentLoggedInUser != isLoggedInUser)) { + triggerMpidChangeListenerCallbacks(newMpid, previousMpid) + } + } + + open val mpid: Long + get() { + return getMpid(false) + } + + fun getMpid(allowTemporary: Boolean): Long { + if (allowTemporary && sInProgress) { + return Constants.TEMPORARY_MPID + } else { + return sPreferences?.getLong(Constants.PrefKeys.MPID, Constants.TEMPORARY_MPID) ?: Constants.TEMPORARY_MPID + } + } + + fun mpidExists(mpid: Long): Boolean { + return mContext?.let { getMpIdSet(it).contains(mpid) } ?: false + } + + val mpids: Set + get() { + return mContext?.let { getMpIdSet(it) } ?: emptySet() + } + + fun mergeUserConfigs(subjectMpId: Long, targetMpId: Long) { + val subjectUserStorage: UserStorage? = getUserStorage(subjectMpId) + val targetUserStorage: UserStorage? = getUserStorage(targetMpId) + subjectUserStorage?.let { + targetUserStorage?.merge(subjectUserStorage) + } + } + + fun shouldTrigger(message: BaseMPMessage): Boolean { + val messageMatches: JSONArray? = triggerMessageMatches + val triggerHashes: JSONArray? = triggerMessageHashes + + var isBackgroundAst: Boolean = false + try { + isBackgroundAst = + (message.messageType == Constants.MessageType.APP_STATE_TRANSITION && message.get(Constants.MessageKey.STATE_TRANSITION_TYPE) == Constants.StateTransitionType.STATE_TRANS_BG) + } catch (ex: JSONException) { + } + var shouldTrigger: Boolean = message.messageType == Constants.MessageType.PUSH_RECEIVED || + message.messageType == Constants.MessageType.COMMERCE_EVENT || + isBackgroundAst + + if (!shouldTrigger && messageMatches != null && messageMatches.length() > 0) { + shouldTrigger = true + var i: Int = 0 + while (shouldTrigger && i < messageMatches.length()) { + try { + val messageMatch: JSONObject = messageMatches.getJSONObject(i) + val keys: Iterator<*> = messageMatch.keys() + while (shouldTrigger && keys.hasNext()) { + val key: String = keys.next() as String + shouldTrigger = message.has(key) + if (shouldTrigger) { + try { + shouldTrigger = messageMatch.getString(key).equals(message.getString(key), ignoreCase = true) + } catch (stringex: JSONException) { + try { + shouldTrigger = message.getBoolean(key) == messageMatch.getBoolean(key) + } catch (boolex: JSONException) { + try { + shouldTrigger = message.getDouble(key) == messageMatch.getDouble(key) + } catch (doubleex: JSONException) { + shouldTrigger = false + } + } + } + } + } + } catch (e: Exception) { + } + i++ + } + } + if (!shouldTrigger && triggerHashes != null) { + for (i in 0 until triggerHashes.length()) { + try { + if (triggerHashes.getInt(i) == message.typeNameHash) { + shouldTrigger = true + break + } + } catch (jse: JSONException) { + } + } + } + return shouldTrigger + } + + val userBucket: Int + get() { + if (mUserBucket < 0) { + mUserBucket = (abs((mpid shr 8).toDouble()) % 100).toInt() + } + return mUserBucket + } + + fun setIntegrationAttributes(integrationId: Int, newAttributes: Map?) { + try { + var newJsonAttributes: JSONObject? = null + if (newAttributes != null && !newAttributes.isEmpty()) { + newJsonAttributes = JSONObject() + for (entry: Map.Entry in newAttributes.entries) { + newJsonAttributes.put(entry.key, entry.value) + } + } + var currentJsonAttributes: JSONObject? = integrationAttributes + if (currentJsonAttributes == null) { + currentJsonAttributes = JSONObject() + } + currentJsonAttributes.put(integrationId.toString(), newJsonAttributes) + if (currentJsonAttributes.length() > 0) { + sPreferences?.edit() + ?.putString(Constants.PrefKeys.INTEGRATION_ATTRIBUTES, currentJsonAttributes.toString()) + ?.apply() + } else { + sPreferences?.edit() + ?.remove(Constants.PrefKeys.INTEGRATION_ATTRIBUTES) + ?.apply() + } + } catch (jse: JSONException) { + } + } + + fun getIntegrationAttributes(integrationId: Int): Map { + val integrationAttributes: MutableMap = HashMap() + val jsonAttributes: JSONObject? = this.integrationAttributes + if (jsonAttributes != null) { + val kitAttributes: JSONObject? = jsonAttributes.optJSONObject(integrationId.toString()) + if (kitAttributes != null) { + try { + val keys: Iterator = kitAttributes.keys() + while (keys.hasNext()) { + val key: String = keys.next() + if (kitAttributes.get(key) is String) { + integrationAttributes[key] = kitAttributes.getString(key) + } + } + } catch (e: JSONException) { + } + } + } + return integrationAttributes + } + + val integrationAttributes: JSONObject? + get() { + var jsonAttributes: JSONObject? = null + val allAttributes: String? = + sPreferences?.getString(Constants.PrefKeys.INTEGRATION_ATTRIBUTES, null) + if (allAttributes != null) { + try { + jsonAttributes = JSONObject(allAttributes) + } catch (e: JSONException) { + } + } + return jsonAttributes + } + + fun getUserIdentities(mpId: Long): Map { + val userIdentitiesJson: JSONArray = getUserIdentityJson(mpId) + val identityTypeStringMap: MutableMap = HashMap(userIdentitiesJson.length()) + + for (i in 0 until userIdentitiesJson.length()) { + try { + val identity: JSONObject = userIdentitiesJson.getJSONObject(i) + identityTypeStringMap[IdentityType.parseInt(identity.getInt(Constants.MessageKey.IDENTITY_NAME))] = + identity.getString(Constants.MessageKey.IDENTITY_VALUE) + } catch (jse: JSONException) { + } + } + + return identityTypeStringMap + } + + val userIdentityJson: JSONArray + get() { + return getUserIdentityJson(mpid) + } + + fun getUserIdentityJson(mpId: Long): JSONArray { + var userIdentities: JSONArray? = null + val userIds: String? = getUserStorage(mpId)?.userIdentities + + try { + userIdentities = JSONArray(userIds) + val changeMade: Boolean = fixUpUserIdentities(userIdentities) + if (changeMade) { + saveUserIdentityJson(userIdentities, mpId) + } + } catch (e: Exception) { + userIdentities = JSONArray() + } + return userIdentities ?: JSONArray() + } + + @JvmOverloads + fun saveUserIdentityJson(userIdentities: JSONArray, mpId: Long = mpid) { + getUserStorage(mpId)?.userIdentities = userIdentities.toString() + } + + open val deviceApplicationStamp: String? + get() { + var das: String? = + sPreferences?.getString(Constants.PrefKeys.DEVICE_APPLICATION_STAMP, null) + if (isEmpty(das)) { + das = UUID.randomUUID().toString() + sPreferences?.edit() + ?.putString(Constants.PrefKeys.DEVICE_APPLICATION_STAMP, das) + ?.apply() + } + return das + } + + fun getCookies(mpId: Long): JSONObject? { + if (mCurrentCookies == null) { + val currentCookies: String? = getUserStorage(mpId)?.cookies + if (isEmpty(currentCookies)) { + mCurrentCookies = JSONObject() + getUserStorage(mpId)?.cookies = mCurrentCookies.toString() + return mCurrentCookies + } else { + try { + mCurrentCookies = JSONObject(currentCookies) + } catch (e: JSONException) { + mCurrentCookies = JSONObject() + } + } + val nowCalendar: Calendar = Calendar.getInstance() + nowCalendar.set(Calendar.YEAR, 1990) + val oldDate: Date = nowCalendar.time + val parser: SimpleDateFormat = SimpleDateFormat("yyyy") + val keys: MutableIterator? = mCurrentCookies?.keys() + val keysToRemove: ArrayList = ArrayList() + while (keys?.hasNext() == true) { + try { + val key: String = keys.next() + if (mCurrentCookies?.get(key) is JSONObject) { + val expiration: String = (mCurrentCookies?.get(key) as JSONObject).getString("e") + try { + val date: Date? = parser.parse(expiration) + if (date?.before(oldDate) == true) { + keysToRemove.add(key) + } + } catch (dpe: ParseException) { + } + } + } catch (jse: JSONException) { + } + } + for (key: String? in keysToRemove) { + mCurrentCookies?.remove(key) + } + if (keysToRemove.size > 0) { + getUserStorage(mpId)?.cookies = mCurrentCookies.toString() + } + return mCurrentCookies + } else { + return mCurrentCookies + } + } + + fun markIdentitiesAsSeen(uploadedIdentities: JSONArray): JSONArray? { + return markIdentitiesAsSeen(uploadedIdentities, mpid) + } + + fun markIdentitiesAsSeen(uploadedIdentities: JSONArray, mpId: Long): JSONArray? { + var uploadedIdentities: JSONArray = uploadedIdentities + try { + val currentIdentities: JSONArray = getUserIdentityJson(mpId) + if (currentIdentities.length() == 0) { + return null + } + uploadedIdentities = JSONArray(uploadedIdentities.toString()) + val identityTypes: MutableSet = HashSet() + for (i in 0 until uploadedIdentities.length()) { + if (uploadedIdentities.getJSONObject(i).optBoolean(Constants.MessageKey.IDENTITY_FIRST_SEEN)) { + identityTypes.add(uploadedIdentities.getJSONObject(i).getInt(Constants.MessageKey.IDENTITY_NAME)) + } + } + if (identityTypes.size > 0) { + for (i in 0 until currentIdentities.length()) { + val identity: Int = currentIdentities.getJSONObject(i).getInt(Constants.MessageKey.IDENTITY_NAME) + if (identityTypes.contains(identity)) { + currentIdentities.getJSONObject(i).put(Constants.MessageKey.IDENTITY_FIRST_SEEN, false) + } + } + return currentIdentities + } + } catch (jse: JSONException) { + } + return null + } + + var identityApiContext: String? + get() { + return sPreferences?.getString(Constants.PrefKeys.IDENTITY_API_CONTEXT, null) + } + set(context) { + sPreferences?.edit()?.putString(Constants.PrefKeys.IDENTITY_API_CONTEXT, context)?.apply() + } + + val previousAdId: String? + get() { + val adInfo: AdIdInfo? = mContext?.let { getAdIdInfo(it) } + if (adInfo != null && !adInfo.isLimitAdTrackingEnabled) { + return sPreferences?.getString(Constants.PrefKeys.PREVIOUS_ANDROID_ID, null) + } + return null + } + + fun setPreviousAdId() { + val adInfo: AdIdInfo? = mContext?.let { getAdIdInfo(it) } + if (adInfo != null && !adInfo.isLimitAdTrackingEnabled) { + sPreferences?.edit()?.putString(Constants.PrefKeys.PREVIOUS_ANDROID_ID, adInfo.id)?.apply() + } else { + sPreferences?.edit()?.remove(Constants.PrefKeys.PREVIOUS_ANDROID_ID)?.apply() + } + } + + var identityConnectionTimeout: Int + get() { + return ( + sPreferences?.getInt( + Constants.PrefKeys.IDENTITY_CONNECTION_TIMEOUT, + DEFAULT_CONNECTION_TIMEOUT_SECONDS + ) ?: DEFAULT_CONNECTION_TIMEOUT_SECONDS + ) * 1000 + } + set(connectionTimeout) { + if (connectionTimeout >= MINIMUM_CONNECTION_TIMEOUT_SECONDS) { + sPreferences?.edit() + ?.putInt(Constants.PrefKeys.IDENTITY_CONNECTION_TIMEOUT, connectionTimeout)?.apply() + } + } + + val connectionTimeout: Int + get() { + return DEFAULT_CONNECTION_TIMEOUT_SECONDS * 1000 + } + + private fun triggerMpidChangeListenerCallbacks(mpid: Long, previousMpid: Long) { + if (isEmpty(mpIdChangeListeners)) { + return + } + for (listenerRef: MpIdChangeListener? in ArrayList(mpIdChangeListeners)) { + if (listenerRef != null) { + listenerRef.onMpIdChanged(mpid, previousMpid) + } + } + } + + fun getConsentState(mpid: Long): ConsentState { + val serializedConsent: String? = getUserStorage(mpid)?.serializedConsentState + return ConsentState.withConsentState(serializedConsent ?: "").build() + } + + val podPrefix: String + /* This function is called to get the specific pod/silo prefix when the `directUrlRouting` is `true`. mParticle API keys are prefixed with the + silo and a hyphen (ex. "us1-", "us2-", "eu1-"). us1 was the first silo,and before other silos existed, there were no prefixes and all apiKeys + were us1. As such, if we split on a '-' and the resulting array length is 1, then it is an older APIkey that should route to us1. + When splitKey.length is greater than 1, then splitKey[0] will be us1, us2, eu1, au1, or st1, etc as new silos are added */ + get() { + var prefix: String = "us1" + try { + val prefixFromApi: Array = apiKey.split("-".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + if (prefixFromApi.size > 1) { + prefix = prefixFromApi.get(0) + } + } catch (e: Exception) { + prefix = "us1" + Logger.error("Error while getting pod prefix for direct URL routing : $e") + } + return prefix + } + + fun setConsentState(state: ConsentState?, mpid: Long) { + var serializedConsent: String? = null + if (state != null) { + serializedConsent = state.toString() + } + getUserStorage(mpid)?.serializedConsentState = serializedConsent + } + + @set:Synchronized + open var networkOptions: NetworkOptions? + get() { + if (sNetworkOptions == null) { + sNetworkOptions = NetworkOptionsManager.validateAndResolve(null) + } + return sNetworkOptions + } + set(networkOptions) { + sNetworkOptions = networkOptions + sPreferences?.edit()?.remove(Constants.PrefKeys.NETWORK_OPTIONS)?.apply() + } + + open val workspaceToken: String + get() { + return sPreferences?.getString(WORKSPACE_TOKEN, "") ?: "" + } + open val aliasMaxWindow: Int + /** + * the maximum allowed age of "start_time" in an AliasRequest, in days + * + * @return + */ + get() { + return sPreferences?.getInt( + ALIAS_MAX_WINDOW, + DEFAULT_MAX_ALIAS_WINDOW_DAYS + ) ?: DEFAULT_MAX_ALIAS_WINDOW_DAYS + } + + val etag: String? + get() { + return sPreferences?.getString(Constants.PrefKeys.ETAG, null) + } + + val ifModified: String? + get() { + return sPreferences?.getString(Constants.PrefKeys.IF_MODIFIED, null) + } + + fun addConfigUpdatedListener(listener: ConfigLoadedListener) { + configUpdatedListeners.add(listener) + } + + private fun onConfigLoaded(configType: ConfigType, isNew: Boolean) { + Logger.debug("Loading " + (if (isNew) "new " else "cached ") + configType.name.lowercase() + " config") + val listeners = configUpdatedListeners + for (listener in listeners) { + listener.onConfigUpdated(configType, isNew) + } + } + + fun parseDataplanOptions(jsonObject: JSONObject?): DataplanOptions? { + if (jsonObject != null) { + val dataplanConfig: JSONObject? = jsonObject.optJSONObject(DATAPLAN_KEY) + if (dataplanConfig != null) { + val dataplanContanier: JSONObject? = dataplanConfig.optJSONObject(DATAPLAN_OBJ) + if (dataplanContanier != null) { + val block: JSONObject? = dataplanContanier.optJSONObject(DATAPLAN_BLOCKING) + val dataplanVersion: JSONObject? = dataplanContanier.optJSONObject(DATAPLAN_VERSION) + if (block != null) { + return DataplanOptions.builder() + .dataplanVersion(dataplanVersion) + .blockEvents(block.optBoolean(DATAPLAN_BLOCK_EVENTS, false)) + .blockEventAttributes(block.optBoolean(DATAPLAN_BLOCK_EVENT_ATTRIBUTES, false)) + .blockUserAttributes(block.optBoolean(DATAPLAN_BLOCK_USER_ATTRIBUTES, false)) + .blockUserIdentities(block.optBoolean(DATAPLAN_BLOCK_USER_IDENTITIES, false)) + .build() + } + } + } + } + return null + } + + private val appVersion: Int + get() { + try { + val packageInfo = mContext?.packageName?.let { mContext?.packageManager?.getPackageInfo(it, 0) } + return packageInfo?.versionCode ?: throw RuntimeException("Could not get package info.") + } catch (e: PackageManager.NameNotFoundException) { + // should never happen + throw RuntimeException("Could not get package name: $e") + } + } + + enum class ConfigType { + CORE, + KIT + } + + interface ConfigLoadedListener { + fun onConfigUpdated(configType: ConfigType, isNew: Boolean) + } + + companion object { + const val CONFIG_JSON: String = "json" + const val KIT_CONFIG_PREFERENCES: String = "mparticle_config.json" + const val CONFIG_JSON_TIMESTAMP: String = "json_timestamp" + private const val KEY_TRIGGER_ITEMS: String = "tri" + private const val KEY_MESSAGE_MATCHES: String = "mm" + private const val KEY_TRIGGER_ITEM_HASHES: String = "evts" + private const val KEY_INFLUENCE_OPEN: String = "pio" + const val KEY_OPT_OUT: String = "oo" + const val KEY_UNHANDLED_EXCEPTIONS: String = "cue" + const val KEY_PUSH_MESSAGES: String = "pmk" + const val KEY_DIRECT_URL_ROUTING: String = "dur" + const val KEY_EMBEDDED_KITS: String = "eks" + const val KEY_UPLOAD_INTERVAL: String = "uitl" + const val KEY_SESSION_TIMEOUT: String = "stl" + const val VALUE_APP_DEFINED: String = "appdefined" + const val VALUE_CUE_CATCH: String = "forcecatch" + const val PREFERENCES_FILE: String = "mp_preferences" + private const val KEY_DEVICE_PERFORMANCE_METRICS_DISABLED: String = "dpmd" + const val WORKSPACE_TOKEN: String = "wst" + const val ALIAS_MAX_WINDOW: String = "alias_max_window" + const val KEY_RAMP: String = "rp" + const val DATAPLAN_KEY: String = "dpr" + const val DATAPLAN_OBJ: String = "dtpn" + const val DATAPLAN_BLOCKING: String = "blok" + const val DATAPLAN_VERSION: String = "vers" + const val DATAPLAN_BLOCK_EVENTS: String = "ev" + const val DATAPLAN_BLOCK_EVENT_ATTRIBUTES: String = "ea" + const val DATAPLAN_BLOCK_USER_ATTRIBUTES: String = "ua" + const val DATAPLAN_BLOCK_USER_IDENTITIES: String = "id" + const val KIT_CONFIG_KEY: String = "kit_config" + const val MIGRATED_TO_KIT_SHARED_PREFS: String = "is_mig_kit_sp" + + private val DEVMODE_UPLOAD_INTERVAL_MILLISECONDS: Int = 10 * 1000 + private const val DEFAULT_MAX_ALIAS_WINDOW_DAYS: Int = 90 + private var sNetworkOptions: NetworkOptions? = null + var sPreferences: SharedPreferences? = null + + private var sPushKeys: JSONArray? = null + const val DEFAULT_CONNECTION_TIMEOUT_SECONDS: Int = 30 + const val MINIMUM_CONNECTION_TIMEOUT_SECONDS: Int = 1 + const val DEFAULT_SESSION_TIMEOUT_SECONDS: Int = 60 + const val DEFAULT_UPLOAD_INTERVAL: Int = 600 + + @JvmStatic + fun getInstance(context: Context): ConfigManager { + var configManager: ConfigManager? = null + val mParticle: MParticle? = MParticle.getInstance() + if (mParticle != null) { + configManager = MParticle.getInstance()?.Internal()?.configManager + } + if (configManager == null) { + configManager = ConfigManager(context) + } + return configManager + } + + fun getUserStorage(context: Context): UserStorage { + return create(context, getMpid(context)) + } + + fun getUserStorage(context: Context, mpid: Long): UserStorage { + return create(context, mpid) + } + + @JvmStatic + fun deleteConfigManager(context: Context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + context.deleteSharedPreferences(PREFERENCES_FILE) + sPreferences = context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE) + } else { + if (sPreferences == null) { + sPreferences = context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE) + } + sPreferences?.edit()?.clear()?.commit() + } + } + + @JvmStatic + fun getEnvironment(): MParticle.Environment { + if (sPreferences != null) { + val env = sPreferences?.getInt(Constants.PrefKeys.ENVIRONMENT, MParticle.Environment.Production.value) + for (environment in MParticle.Environment.entries.toTypedArray()) { + if (environment.value == env) { + return environment + } + } + } + return MParticle.Environment.Production + } + + @JvmStatic + fun isDisplayPushNotifications(context: Context): Boolean { + return getPreferences(context).getBoolean(Constants.PrefKeys.DISPLAY_PUSH_NOTIFICATIONS, false) + } + + fun getPreferences(context: Context): SharedPreferences { + return context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE) + } + + @JvmStatic + fun getPushKeys(context: Context): JSONArray { + if (sPushKeys == null) { + val arrayString: String? = getPreferences(context).getString(KEY_PUSH_MESSAGES, null) + try { + sPushKeys = JSONArray(arrayString) + } catch (e: Exception) { + sPushKeys = JSONArray() + } + } + return sPushKeys ?: JSONArray() + } + + @JvmStatic + fun getPushTitle(context: Context): Int { + return getPreferences(context) + .getInt(Constants.PrefKeys.PUSH_TITLE, 0) + } + + @JvmStatic + fun getPushIcon(context: Context): Int { + return getPreferences(context) + .getInt(Constants.PrefKeys.PUSH_ICON, 0) + } + + fun getBreadcrumbLimit(context: Context): Int { + return getUserStorage(context).breadcrumbLimit + } + + @JvmStatic + fun getBreadcrumbLimit(context: Context, mpId: Long): Int { + return getUserStorage(context, mpId).breadcrumbLimit + } + + fun getCurrentUserLtv(context: Context): String? { + return getUserStorage(context).ltv + } + + @JvmStatic + fun setNeedsToMigrate(context: Context, needsToMigrate: Boolean) { + UserStorage.setNeedsToMigrate(context, needsToMigrate) + } + + fun clear() { + sPreferences?.edit()?.clear()?.apply() + } + + // for testing + @JvmStatic + fun clearMpid(context: Context) { + if (sPreferences == null) { + sPreferences = context.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE) + } + sPreferences?.edit()?.remove(Constants.PrefKeys.MPID)?.apply() + } + + @JvmStatic + fun getMpid(context: Context?): Long { + return getMpid(context, false) + } + + fun getMpid(context: Context?, allowTemporary: Boolean): Long { + if (sPreferences == null) { + sPreferences = context?.getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE) + } + if (allowTemporary && sInProgress) { + return Constants.TEMPORARY_MPID + } else { + return sPreferences?.getLong(Constants.PrefKeys.MPID, Constants.TEMPORARY_MPID) ?: Constants.TEMPORARY_MPID + } + } + + private var sInProgress: Boolean = false + + @JvmStatic + fun setIdentityRequestInProgress(inProgress: Boolean) { + sInProgress = inProgress + } + + private fun fixUpUserIdentities(identities: JSONArray): Boolean { + var changeMade: Boolean = false + try { + for (i in 0 until identities.length()) { + val identity: JSONObject = identities.getJSONObject(i) + if (!identity.has(Constants.MessageKey.IDENTITY_DATE_FIRST_SEEN)) { + identity.put(Constants.MessageKey.IDENTITY_DATE_FIRST_SEEN, 0) + changeMade = true + } + if (!identity.has(Constants.MessageKey.IDENTITY_FIRST_SEEN)) { + identity.put(Constants.MessageKey.IDENTITY_FIRST_SEEN, true) + changeMade = true + } + } + } catch (jse: JSONException) { + } + return changeMade + } + + private val mpIdChangeListeners: MutableSet = HashSet() + + @JvmStatic + fun addMpIdChangeListener(listener: MpIdChangeListener?) { + mpIdChangeListeners.add(listener) + } + } +} 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 18c253cd5..12311c7eb 100644 --- a/android-core/src/test/kotlin/com/mparticle/internal/ConfigManagerTest.kt +++ b/android-core/src/test/kotlin/com/mparticle/internal/ConfigManagerTest.kt @@ -84,7 +84,7 @@ class ConfigManagerTest { null ) Assert.assertEquals("key2", manager.apiKey) - Assert.assertNull(manager.apiSecret) + Assert.assertEquals("", manager.apiSecret) Assert.assertEquals(MParticle.Environment.Development, ConfigManager.getEnvironment()) } @@ -96,7 +96,7 @@ class ConfigManagerTest { json.put("test", "value") manager.saveConfigJson(json) val `object` = - ConfigManager.sPreferences.getString(ConfigManager.CONFIG_JSON, null) + ConfigManager.sPreferences?.getString(ConfigManager.CONFIG_JSON, null) ?.let { JSONObject(it) } Assert.assertNotNull(`object`) } @@ -115,7 +115,7 @@ class ConfigManagerTest { Assert.assertEquals(5, ConfigManager.getPushKeys(context).length().toLong()) manager.updateConfig(JSONObject()) val `object` = - ConfigManager.sPreferences.getString(ConfigManager.CONFIG_JSON, null) + ConfigManager.sPreferences?.getString(ConfigManager.CONFIG_JSON, null) ?.let { JSONObject(it) } if (`object` != null) { Assert.assertTrue(!`object`.keys().hasNext()) @@ -128,7 +128,7 @@ class ConfigManagerTest { manager.updateConfig(JSONObject(sampleConfig)) manager.reloadCoreConfig(JSONObject()) val `object` = - ConfigManager.sPreferences.getString(ConfigManager.CONFIG_JSON, null) + ConfigManager.sPreferences?.getString(ConfigManager.CONFIG_JSON, null) ?.let { JSONObject(it) } `object`?.keys()?.hasNext()?.let { Assert.assertTrue(it) } } @@ -188,7 +188,7 @@ class ConfigManagerTest { @Throws(Exception::class) fun testGetTriggerMessageMatches() { val triggerMessageMatches = manager.triggerMessageMatches - Assert.assertEquals(1, triggerMessageMatches.length().toLong()) + Assert.assertEquals(1, triggerMessageMatches?.length()) } @Test @@ -337,9 +337,11 @@ class ConfigManagerTest { @Throws(Exception::class) fun testGetTriggerMessageHashes() { val hashes = manager.triggerMessageHashes - for (i in 0 until hashes.length()) { - val hash = hashes.getInt(i) - Assert.assertTrue(hash == 1217787541 || hash == 2 || hash == 3) + if (hashes != null) { + for (i in 0 until hashes.length()) { + val hash = hashes.getInt(i) + Assert.assertTrue(hash == 1217787541 || hash == 2 || hash == 3) + } } } @@ -380,34 +382,34 @@ class ConfigManagerTest { @Test @Throws(Exception::class) fun testSetNullIntegrationAttributes() { - Assert.assertFalse(ConfigManager.sPreferences.contains(ATTRIBUTES)) + ConfigManager.sPreferences?.contains(ATTRIBUTES)?.let { Assert.assertFalse(it) } manager.setIntegrationAttributes(1, null) - Assert.assertFalse(ConfigManager.sPreferences.contains(ATTRIBUTES)) - ConfigManager.sPreferences.edit() - .putString(ATTRIBUTES, "{\"1\":{\"test-key\":\"test-value\"}}").apply() - Assert.assertTrue(ConfigManager.sPreferences.contains(ATTRIBUTES)) + ConfigManager.sPreferences?.contains(ATTRIBUTES)?.let { Assert.assertFalse(it) } + ConfigManager.sPreferences?.edit() + ?.putString(ATTRIBUTES, "{\"1\":{\"test-key\":\"test-value\"}}")?.apply() + ConfigManager.sPreferences?.let { Assert.assertTrue(it.contains(ATTRIBUTES)) } manager.setIntegrationAttributes(1, null) - Assert.assertFalse(ConfigManager.sPreferences.contains(ATTRIBUTES)) + ConfigManager.sPreferences?.let { Assert.assertFalse(it.contains(ATTRIBUTES)) } } @Test @Throws(Exception::class) fun testSetEmptyIntegrationAttributes() { - Assert.assertFalse(ConfigManager.sPreferences.contains(ATTRIBUTES)) + ConfigManager.sPreferences?.let { Assert.assertFalse(it.contains(ATTRIBUTES)) } val attributes: Map = HashMap() manager.setIntegrationAttributes(1, attributes) - Assert.assertFalse(ConfigManager.sPreferences.contains(ATTRIBUTES)) - ConfigManager.sPreferences.edit() - .putString(ATTRIBUTES, "{\"1\":{\"test-key\":\"test-value\"}}").apply() - Assert.assertTrue(ConfigManager.sPreferences.contains(ATTRIBUTES)) + ConfigManager.sPreferences?.let { Assert.assertFalse(it.contains(ATTRIBUTES)) } + ConfigManager.sPreferences?.edit() + ?.putString(ATTRIBUTES, "{\"1\":{\"test-key\":\"test-value\"}}")?.apply() + ConfigManager.sPreferences?.let { Assert.assertTrue(it.contains(ATTRIBUTES)) } manager.setIntegrationAttributes(1, attributes) - Assert.assertFalse(ConfigManager.sPreferences.contains(ATTRIBUTES)) + ConfigManager.sPreferences?.let { Assert.assertFalse(it.contains(ATTRIBUTES)) } } @Test @Throws(Exception::class) fun testSetNonEmptyIntegrationAttributes() { - Assert.assertFalse(ConfigManager.sPreferences.contains(ATTRIBUTES)) + ConfigManager.sPreferences?.let { Assert.assertFalse(it.contains(ATTRIBUTES)) } val attributes: MutableMap = HashMap() attributes["test-key"] = "value 2" manager.setIntegrationAttributes(1, attributes) @@ -415,29 +417,29 @@ class ConfigManagerTest { manager.setIntegrationAttributes(12, attributes) Assert.assertEquals( "{\"1\":{\"test-key\":\"value 2\"},\"12\":{\"test-key\":\"value 3\"}}", - ConfigManager.sPreferences.getString( + ConfigManager.sPreferences?.getString( ATTRIBUTES, null - ) + ) ?: "" ) } @Test @Throws(Exception::class) fun testGetKitIntegrationAttributes() { - Assert.assertFalse(ConfigManager.sPreferences.contains(ATTRIBUTES)) + ConfigManager.sPreferences?.let { Assert.assertFalse(it.contains(ATTRIBUTES)) } Assert.assertEquals(0, manager.getIntegrationAttributes(1).size.toLong()) - ConfigManager.sPreferences.edit().putString( + ConfigManager.sPreferences?.edit()?.putString( ATTRIBUTES, "{\"1\":{\"test-key\":\"value 2\"},\"12\":{\"test-key\":\"value 3\"}}" - ).apply() + )?.apply() var attributes = manager.getIntegrationAttributes(1) Assert.assertEquals(1, attributes.size.toLong()) Assert.assertEquals("value 2", attributes["test-key"]) attributes = manager.getIntegrationAttributes(12) Assert.assertEquals(1, attributes.size.toLong()) Assert.assertEquals("value 3", attributes["test-key"]) - ConfigManager.sPreferences.edit().remove(ATTRIBUTES).apply() + ConfigManager.sPreferences?.edit()?.remove(ATTRIBUTES)?.apply() Assert.assertEquals(0, manager.getIntegrationAttributes(1).size.toLong()) Assert.assertEquals(0, manager.getIntegrationAttributes(12).size.toLong()) } @@ -445,17 +447,19 @@ class ConfigManagerTest { @Test @Throws(Exception::class) fun testGetAllIntegrationAttributes() { - Assert.assertFalse(ConfigManager.sPreferences.contains(ATTRIBUTES)) + ConfigManager.sPreferences?.let { Assert.assertFalse(it.contains(ATTRIBUTES)) } Assert.assertNull(manager.integrationAttributes) - ConfigManager.sPreferences.edit().putString( + ConfigManager.sPreferences?.edit()?.putString( ATTRIBUTES, "{\"1\":{\"test-key\":\"value 2\"},\"12\":{\"test-key\":\"value 3\"}}" - ).apply() + )?.apply() val attributes = manager.integrationAttributes - Assert.assertEquals(2, attributes.length().toLong()) - Assert.assertEquals("value 2", attributes.getJSONObject("1")["test-key"]) - Assert.assertEquals("value 3", attributes.getJSONObject("12")["test-key"]) - ConfigManager.sPreferences.edit().remove(ATTRIBUTES).apply() + Assert.assertEquals(2, attributes?.length()) + Assert.assertEquals("value 2", attributes?.getJSONObject("1")?.get("test-key") ?: "") + if (attributes != null) { + Assert.assertEquals("value 3", attributes.getJSONObject("12")["test-key"]) + } + ConfigManager.sPreferences?.edit()?.remove(ATTRIBUTES)?.apply() Assert.assertNull(manager.integrationAttributes) } @@ -500,8 +504,10 @@ class ConfigManagerTest { Assert.assertNull(manager.markIdentitiesAsSeen(JSONArray())) val seenIdentities = manager.markIdentitiesAsSeen(identities) Assert.assertNotEquals(seenIdentities, identities) - for (i in 0 until seenIdentities.length()) { - Assert.assertFalse(seenIdentities.getJSONObject(i).getBoolean("f")) + if (seenIdentities != null) { + for (i in 0 until seenIdentities.length()) { + Assert.assertFalse(seenIdentities.getJSONObject(i).getBoolean("f")) + } } identities = JSONArray() identities.put(JSONObject("{ \"n\": 1, \"i\": \" value 1\", \"dfs\": 1473869816521, \"f\": true }")) @@ -514,11 +520,15 @@ class ConfigManagerTest { val newIdentities = JSONArray() newIdentities.put(JSONObject("{ \"n\": 1, \"i\": \" value 1\", \"dfs\": 1473869816521, \"f\": true }")) val updatedIdentities = manager.markIdentitiesAsSeen(newIdentities) - Assert.assertEquals(4, updatedIdentities.length().toLong()) - for (i in 0 until updatedIdentities.length()) { - when (updatedIdentities.getJSONObject(i).getInt("n")) { - 1, 4 -> Assert.assertFalse(updatedIdentities.getJSONObject(i).getBoolean("f")) - else -> Assert.assertTrue(updatedIdentities.getJSONObject(i).getBoolean("f")) + if (updatedIdentities != null) { + Assert.assertEquals(4, updatedIdentities.length().toLong()) + } + if (updatedIdentities != null) { + for (i in 0 until updatedIdentities.length()) { + when (updatedIdentities.getJSONObject(i).getInt("n")) { + 1, 4 -> Assert.assertFalse(updatedIdentities.getJSONObject(i).getBoolean("f")) + else -> Assert.assertTrue(updatedIdentities.getJSONObject(i).getBoolean("f")) + } } } } diff --git a/android-core/src/test/kotlin/com/mparticle/internal/MessageManagerTest.kt b/android-core/src/test/kotlin/com/mparticle/internal/MessageManagerTest.kt index 82fb801fd..975a68964 100644 --- a/android-core/src/test/kotlin/com/mparticle/internal/MessageManagerTest.kt +++ b/android-core/src/test/kotlin/com/mparticle/internal/MessageManagerTest.kt @@ -156,7 +156,7 @@ class MessageManagerTest { Assert.assertFalse(sessionStart.has(MessageKey.PREVIOUS_SESSION_START)) Assert.assertEquals( appStateManager.session.mSessionID, - configManager.userStorage.getPreviousSessionId(null) + configManager.userStorage?.getPreviousSessionId(null) ) Assert.assertFalse( prefs.getBoolean( @@ -164,8 +164,8 @@ class MessageManagerTest { true ) ) - configManager.userStorage.setPreviousSessionForeground(42000) - configManager.userStorage.setPreviousSessionStart(24000) + configManager.userStorage?.setPreviousSessionForeground(42000) + configManager.userStorage?.setPreviousSessionStart(24000) prefs.commit() sessionStart = manager.startSession(appStateManager.session) Assert.assertNotNull(sessionStart) @@ -179,13 +179,13 @@ class MessageManagerTest { @Test fun testIncrementSessionCounter() { - var count = configManager.userStorage.getCurrentSessionCounter(-5) - Assert.assertEquals(-5, count.toLong()) + var count = configManager.userStorage?.getCurrentSessionCounter(-5) + Assert.assertEquals(-5, count) for (i in 0..9) { - configManager.userStorage.incrementSessionCounter() + configManager.userStorage?.incrementSessionCounter() } - count = configManager.userStorage.getCurrentSessionCounter(-5) - Assert.assertEquals(10, count.toLong()) + count = configManager.userStorage?.getCurrentSessionCounter(-5) + Assert.assertEquals(10, count) } @Test @@ -204,8 +204,8 @@ class MessageManagerTest { Message::class.java ) ) - val time = configManager.userStorage.getPreviousSessionForegound(-1) - Assert.assertEquals(5000, time) + val time = configManager.userStorage?.getPreviousSessionForegound(-1) + Assert.assertEquals(5000, time?.toInt()) } @Test @@ -353,7 +353,7 @@ class MessageManagerTest { ) Assert.assertEquals( message.getInt(MessageKey.BREADCRUMB_SESSION_COUNTER).toLong(), - configManager.userStorage.currentSessionCounter.toLong() + configManager.userStorage?.currentSessionCounter?.toLong() ) Assert.assertEquals(message.getString(MessageKey.BREADCRUMB_LABEL), "test crumb") Mockito.verify(messageHandler, Mockito.times(2)).sendMessage( @@ -410,7 +410,7 @@ class MessageManagerTest { ) Assert.assertEquals( message.getInt(MessageKey.ERROR_SESSION_COUNT).toLong(), - configManager.userStorage.currentSessionCounter.toLong() + configManager.userStorage?.currentSessionCounter?.toLong() ) Mockito.verify(messageHandler, Mockito.times(3)).sendMessage( Mockito.any( @@ -432,7 +432,7 @@ class MessageManagerTest { ) Assert.assertEquals( message.getInt(MessageKey.ERROR_SESSION_COUNT).toLong(), - configManager.userStorage.currentSessionCounter.toLong() + configManager.userStorage?.currentSessionCounter?.toLong() ) Mockito.verify(messageHandler, Mockito.times(4)).sendMessage( Mockito.any( @@ -454,7 +454,7 @@ class MessageManagerTest { ) Assert.assertEquals( message.getInt(MessageKey.ERROR_SESSION_COUNT).toLong(), - configManager.userStorage.currentSessionCounter.toLong() + configManager.userStorage?.currentSessionCounter?.toLong() ) Mockito.verify(messageHandler, Mockito.times(5)).sendMessage( Mockito.any( From 5cfe82ec1bc3a83ffb51cfd47295072d32e2ac71 Mon Sep 17 00:00:00 2001 From: Mansi Pandya Date: Thu, 6 Feb 2025 16:12:37 -0500 Subject: [PATCH 2/2] address review comment --- .../main/kotlin/com/mparticle/internal/ConfigManager.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/android-core/src/main/kotlin/com/mparticle/internal/ConfigManager.kt b/android-core/src/main/kotlin/com/mparticle/internal/ConfigManager.kt index 719b07ad1..1dab5dab6 100644 --- a/android-core/src/main/kotlin/com/mparticle/internal/ConfigManager.kt +++ b/android-core/src/main/kotlin/com/mparticle/internal/ConfigManager.kt @@ -244,11 +244,9 @@ open class ConfigManager { } } set(timestamp) { - if (timestamp != null) { - sPreferences?.edit() - ?.putLong(CONFIG_JSON_TIMESTAMP, timestamp) - ?.apply() - } + sPreferences?.edit() + ?.putLong(CONFIG_JSON_TIMESTAMP, timestamp ?: 0L) + ?.apply() } @JvmOverloads