From 9da38b85f8eb4556b68a6a7f808cc67e9fbad4b3 Mon Sep 17 00:00:00 2001 From: Will Passidomo Date: Mon, 21 Mar 2022 12:42:03 -0400 Subject: [PATCH] feat: improve kit loading performance - cache parsed kit configuration instance so we do not need to re-parse the same settings on kit reloads - move kit configuration parsing to a background thread --- .../java/com/mparticle/MParticleTest.java | 2 +- .../AppStateManagerInstrumentedTest.java | 1 - .../internal/KitFrameworkWrapperTest.java | 2 +- .../java/com/mparticle/MPServiceUtil.java | 36 ++- .../main/java/com/mparticle/MParticle.java | 2 +- .../com/mparticle/internal/ConfigManager.java | 17 +- .../com/mparticle/internal/CoreCallbacks.java | 4 +- .../internal/KitFrameworkWrapper.java | 76 +++-- .../com/mparticle/internal/KitManager.java | 2 +- .../mparticle/internal/KitsLoadedCallback.kt | 29 ++ .../KitsLoadedListenerConfiguration.kt | 8 + .../mparticle/internal/MessageManager.java | 2 +- .../java/com/mparticle/MockMParticle.java | 4 + .../mparticle/internal/ConfigManagerTest.java | 8 - .../internal/KitFrameworkWrapperTest.java | 19 -- .../internal/MParticleApiClientImplTest.java | 15 +- .../mparticle/internal/MockKitManager.java | 263 ------------------ .../kits/KnownUserKitsLifecycleTest.java | 11 +- .../com/mparticle/kits/BaseKitOptionsTest.kt | 43 ++- .../mparticle/kits/ConfiguredKitOptions.kt | 4 +- .../com/mparticle/kits/UpdateConfigTest.kt | 17 -- .../com/mparticle/kits/KitIntegration.java | 2 +- .../com/mparticle/kits/KitManagerImpl.java | 160 ++++++----- .../mparticle/kits/KitManagerImplTest.java | 88 +++--- testutils/build.gradle | 2 - .../com/mparticle/internal/AccessUtils.java | 2 +- .../com/mparticle/mock/MockCoreCallbacks.java | 3 - .../mparticle/mock/MockKitManagerImpl.java | 19 +- .../mparticle/testutils/BaseAbstractTest.java | 18 +- .../java/com/mparticle/testutils/MPLatch.java | 34 ++- 30 files changed, 338 insertions(+), 555 deletions(-) create mode 100644 android-core/src/main/java/com/mparticle/internal/KitsLoadedCallback.kt create mode 100644 android-core/src/main/java/com/mparticle/internal/KitsLoadedListenerConfiguration.kt delete mode 100644 android-core/src/test/java/com/mparticle/internal/MockKitManager.java diff --git a/android-core/src/androidTest/java/com/mparticle/MParticleTest.java b/android-core/src/androidTest/java/com/mparticle/MParticleTest.java index df01f0a1e..fef360cb3 100644 --- a/android-core/src/androidTest/java/com/mparticle/MParticleTest.java +++ b/android-core/src/androidTest/java/com/mparticle/MParticleTest.java @@ -101,7 +101,7 @@ public void installReferrerUpdated() { } }; - MParticle.getInstance().mKitManager = new KitFrameworkWrapper(mContext, null,null, null, null, true, null) { + MParticle.getInstance().mKitManager = new KitFrameworkWrapper(mContext, null,null, null, true, null) { @Override public void installReferrerUpdated() { called[1] = true; diff --git a/android-core/src/androidTest/java/com/mparticle/internal/AppStateManagerInstrumentedTest.java b/android-core/src/androidTest/java/com/mparticle/internal/AppStateManagerInstrumentedTest.java index a52a06fa6..38aabd46c 100644 --- a/android-core/src/androidTest/java/com/mparticle/internal/AppStateManagerInstrumentedTest.java +++ b/android-core/src/androidTest/java/com/mparticle/internal/AppStateManagerInstrumentedTest.java @@ -146,7 +146,6 @@ public void logAll(List messageList) { }, MParticle.getInstance().Internal().getConfigManager(), MParticle.getInstance().Internal().getAppStateManager(), - com.mparticle.internal.AccessUtils.getUploadHandler(), MParticleOptions.builder(mContext).credentials("some", "key").build()); this.latch = latch; } diff --git a/android-core/src/androidTest/java/com/mparticle/internal/KitFrameworkWrapperTest.java b/android-core/src/androidTest/java/com/mparticle/internal/KitFrameworkWrapperTest.java index 47b22b902..d8b339400 100644 --- a/android-core/src/androidTest/java/com/mparticle/internal/KitFrameworkWrapperTest.java +++ b/android-core/src/androidTest/java/com/mparticle/internal/KitFrameworkWrapperTest.java @@ -145,7 +145,7 @@ public void onModifyCompleted(MParticleUser user, IdentityApiRequest request) { static class StubKitManager extends KitFrameworkWrapper { public StubKitManager(Context context) { - super(context, null, null, null, null, true, null); + super(context, null, null, null, true, null); setKitManager(null); } diff --git a/android-core/src/main/java/com/mparticle/MPServiceUtil.java b/android-core/src/main/java/com/mparticle/MPServiceUtil.java index 14f8860b2..76b0e05cc 100644 --- a/android-core/src/main/java/com/mparticle/MPServiceUtil.java +++ b/android-core/src/main/java/com/mparticle/MPServiceUtil.java @@ -14,9 +14,9 @@ import com.mparticle.internal.ConfigManager; import com.mparticle.internal.Constants; -import com.mparticle.internal.KitFrameworkWrapper; -import com.mparticle.internal.KitsLoadedListener; +import com.mparticle.internal.KitsLoadedListenerConfiguration; import com.mparticle.internal.Logger; +import com.mparticle.internal.KitsLoadedListener; import com.mparticle.messaging.MPMessagingAPI; import com.mparticle.messaging.ProviderCloudMessage; @@ -149,28 +149,26 @@ private void handleNotificationTap(Intent intent) { private void generateCloudMessage(final Intent intent) { try { - KitsLoadedListener kitsLoadedListener = new KitsLoadedListener() { - @Override - public void onKitsLoaded() { - try { - MParticle instance = MParticle.getInstance(); - if (instance != null) { - instance.Internal().getKitManager().loadKitLibrary(); - ProviderCloudMessage cloudMessage = ProviderCloudMessage.createMessage(intent, ConfigManager.getPushKeys(mContext)); - boolean handled = instance.Internal().getKitManager().onMessageReceived(mContext.getApplicationContext(), intent); - cloudMessage.setDisplayed(handled); - broadcastNotificationReceived(cloudMessage); - } - } catch (Exception e) { - Logger.warning("FCM parsing error: " + e.toString()); + KitsLoadedListener kitsLoadedListener = () -> { + try { + MParticle instance = MParticle.getInstance(); + if (instance != null) { + ProviderCloudMessage cloudMessage = ProviderCloudMessage.createMessage(intent, ConfigManager.getPushKeys(mContext)); + boolean handled = instance.Internal().getKitManager().onMessageReceived(mContext.getApplicationContext(), intent); + cloudMessage.setDisplayed(handled); + broadcastNotificationReceived(cloudMessage); } + } catch (Exception e) { + Logger.warning("FCM parsing error: " + e); } }; - KitFrameworkWrapper.addKitsLoadedListener(kitsLoadedListener); - MParticle.start(MParticleOptions.builder(mContext).buildForInternalRestart()); + MParticle.start(MParticleOptions + .builder(mContext) + .configuration(new KitsLoadedListenerConfiguration(kitsLoadedListener)) + .buildForInternalRestart()); } catch (Exception e) { - Logger.warning("FCM parsing error: " + e.toString()); + Logger.warning("FCM parsing error: " + e); } } diff --git a/android-core/src/main/java/com/mparticle/MParticle.java b/android-core/src/main/java/com/mparticle/MParticle.java index b1a1c72ac..a40a5b457 100644 --- a/android-core/src/main/java/com/mparticle/MParticle.java +++ b/android-core/src/main/java/com/mparticle/MParticle.java @@ -153,7 +153,7 @@ private static MParticle getInstance(@NonNull Context context, @NonNull MParticl } instance = new MParticle(options); - instance.mKitManager = new KitFrameworkWrapper(options.getContext(), instance.mMessageManager, instance.Internal().getConfigManager(), instance.Internal().getAppStateManager(), instance.mMessageManager.getTaskHandler(), options); + instance.mKitManager = new KitFrameworkWrapper(options.getContext(), instance.mMessageManager, instance.Internal().getConfigManager(), instance.Internal().getAppStateManager(), options); instance.mIdentityApi = new IdentityApi(options.getContext(), instance.mInternal.getAppStateManager(), instance.mMessageManager, instance.mConfigManager, instance.mKitManager, options.getOperatingSystem()); instance.mMessageManager.refreshConfiguration(); instance.identify(options); diff --git a/android-core/src/main/java/com/mparticle/internal/ConfigManager.java b/android-core/src/main/java/com/mparticle/internal/ConfigManager.java index 5d4bf98d9..6eaca139f 100644 --- a/android-core/src/main/java/com/mparticle/internal/ConfigManager.java +++ b/android-core/src/main/java/com/mparticle/internal/ConfigManager.java @@ -8,6 +8,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; import com.mparticle.Configuration; import com.mparticle.ExceptionHandler; @@ -176,6 +177,7 @@ private void restoreCoreConfig() { * 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, new JSONArray().toString()); if (!MPUtility.isEmpty(oldConfig)) { @@ -354,20 +356,12 @@ public synchronized void reloadCoreConfig(JSONObject responseJSON) throws JSONEx updateCoreConfig(responseJSON, false); } - private synchronized void reloadKitConfig(@Nullable JSONArray kitConfigs) { - //only reload if KitManager has not already been loaded (from new config, presumably) - MParticle instance = MParticle.getInstance(); - if (instance != null ) { - instance.Internal().getKitManager().updateKits(kitConfigs); - onConfigLoaded(ConfigType.KIT, false); - } - } - private synchronized void updateKitConfig(@Nullable JSONArray kitConfigs) { MParticle instance = MParticle.getInstance(); if (instance != null) { - instance.Internal().getKitManager().loadKitLibrary(); - onConfigLoaded(ConfigType.KIT, true); + instance.Internal().getKitManager() + .updateKits(kitConfigs) + .onKitsLoaded(() -> onConfigLoaded(ConfigType.KIT, true)); } } @@ -474,7 +468,6 @@ public boolean getRestrictAAIDBasedOnLAT() { * This method will be called from a background thread after startup is already complete. */ public void delayedStart() { - reloadKitConfig(getLatestKitConfiguration()); String senderId = getPushSenderId(); if (isPushEnabled() && senderId != null) { MParticle.getInstance().Messaging().enablePushNotifications(senderId); diff --git a/android-core/src/main/java/com/mparticle/internal/CoreCallbacks.java b/android-core/src/main/java/com/mparticle/internal/CoreCallbacks.java index 9e37c1ded..a3c925c25 100644 --- a/android-core/src/main/java/com/mparticle/internal/CoreCallbacks.java +++ b/android-core/src/main/java/com/mparticle/internal/CoreCallbacks.java @@ -3,6 +3,8 @@ import android.app.Activity; import android.net.Uri; +import androidx.annotation.WorkerThread; + import com.mparticle.MParticleOptions; import org.json.JSONArray; @@ -17,6 +19,7 @@ public interface CoreCallbacks { void setIntegrationAttributes(int kitId, Map integrationAttributes); Map getIntegrationAttributes(int kitId); WeakReference getCurrentActivity(); + @WorkerThread JSONArray getLatestKitConfiguration(); MParticleOptions.DataplanOptions getDataplanOptions(); boolean isPushEnabled(); @@ -24,7 +27,6 @@ public interface CoreCallbacks { String getPushInstanceId(); Uri getLaunchUri(); String getLaunchAction(); - void replayAndDisableQueue(); KitListener getKitListener(); interface KitListener { diff --git a/android-core/src/main/java/com/mparticle/internal/KitFrameworkWrapper.java b/android-core/src/main/java/com/mparticle/internal/KitFrameworkWrapper.java index 8268921cc..8e7bb22fd 100644 --- a/android-core/src/main/java/com/mparticle/internal/KitFrameworkWrapper.java +++ b/android-core/src/main/java/com/mparticle/internal/KitFrameworkWrapper.java @@ -8,6 +8,7 @@ import android.os.Bundle; import androidx.annotation.Nullable; +import androidx.annotation.WorkerThread; import com.mparticle.AttributionResult; import com.mparticle.BaseEvent; @@ -35,7 +36,6 @@ public class KitFrameworkWrapper implements KitManager { private final Context mContext; final CoreCallbacks mCoreCallbacks; private final ReportingManager mReportingManager; - private final BackgroundTaskHandler mBackgroundTaskHandler; KitManager mKitManager; private final MParticleOptions mOptions; private volatile boolean frameworkLoadAttempted = false; @@ -44,39 +44,47 @@ public class KitFrameworkWrapper implements KitManager { private Queue eventQueue; private Queue attributeQueue; private volatile boolean registerForPush = false; - private static final List kitsLoadedListeners = new ArrayList(); + private static final List kitsLoadedListeners = new ArrayList<>(); - public KitFrameworkWrapper(Context context, ReportingManager reportingManager, ConfigManager configManager, AppStateManager appStateManager, BackgroundTaskHandler backgroundTaskHandler, MParticleOptions options) { - this(context, reportingManager, configManager, appStateManager, backgroundTaskHandler, false, options); + public KitFrameworkWrapper(Context context, ReportingManager reportingManager, ConfigManager configManager, AppStateManager appStateManager, MParticleOptions options) { + this(context, reportingManager, configManager, appStateManager, false, options); } - public KitFrameworkWrapper(Context context, ReportingManager reportingManager, ConfigManager configManager, AppStateManager appStateManager, BackgroundTaskHandler backgroundTaskHandler, boolean testing, MParticleOptions options) { + public KitFrameworkWrapper(Context context, ReportingManager reportingManager, ConfigManager configManager, AppStateManager appStateManager, boolean testing, MParticleOptions options) { this.mOptions = options; this.mContext = testing ? context : new KitContext(context); this.mReportingManager = reportingManager; this.mCoreCallbacks = new CoreCallbacksImpl(this, configManager, appStateManager); - this.mBackgroundTaskHandler = backgroundTaskHandler; kitsLoaded = false; } + @WorkerThread public void loadKitLibrary() { if (!frameworkLoadAttempted) { Logger.debug("Loading Kit Framework."); frameworkLoadAttempted = true; try { Class clazz = Class.forName("com.mparticle.kits.KitManagerImpl"); - Constructor constructor = clazz.getConstructor(Context.class, ReportingManager.class, CoreCallbacks.class, BackgroundTaskHandler.class, MParticleOptions.class); - mKitManager = constructor.newInstance(mContext, mReportingManager, mCoreCallbacks, mBackgroundTaskHandler, mOptions); + Constructor constructor = clazz.getConstructor(Context.class, ReportingManager.class, CoreCallbacks.class, MParticleOptions.class); + KitManager kitManager = constructor.newInstance(mContext, mReportingManager, mCoreCallbacks, mOptions); JSONArray configuration = mCoreCallbacks.getLatestKitConfiguration(); Logger.debug("Kit Framework loaded."); - if (configuration != null) { + if (!MPUtility.isEmpty(configuration)) { Logger.debug("Restoring previous Kit configuration."); - updateKits(configuration); + kitManager + .updateKits(configuration) + .onKitsLoaded(() -> { + mKitManager = kitManager; + setKitsLoaded(true); + } + ); + } else { + mKitManager = kitManager; } updateDataplan(mCoreCallbacks.getDataplanOptions()); } catch (Exception e) { Logger.debug("No Kit Framework detected."); - disableQueuing(); + setKitsLoaded(true); } } } @@ -97,21 +105,28 @@ void setKitManager(KitManager manager) { mKitManager = manager; } - public static boolean getKitsLoaded() { + public boolean getKitsLoaded() { return kitsLoaded; } - public static void addKitsLoadedListener(KitsLoadedListener listener) { - if (kitsLoaded) { - listener.onKitsLoaded(); - } else { - kitsLoadedListeners.add(listener); + public void addKitsLoadedListener(KitsLoadedListener listener) { + if (listener != null) { + if (kitsLoaded) { + listener.onKitsLoaded(); + } else { + kitsLoadedListeners.add(listener); + } } } void setKitsLoaded(boolean kitsLoaded) { this.kitsLoaded = kitsLoaded; - List kitsLoadedListenersCopy = new ArrayList(kitsLoadedListeners); + if (kitsLoaded) { + replayAndDisableQueue(); + } else { + disableQueuing(); + } + List kitsLoadedListenersCopy = new ArrayList<>(kitsLoadedListeners); for (KitsLoadedListener kitsLoadedListener: kitsLoadedListenersCopy) { if (kitsLoadedListener != null) { kitsLoadedListener.onKitsLoaded(); @@ -121,7 +136,6 @@ void setKitsLoaded(boolean kitsLoaded) { } synchronized void disableQueuing() { - setKitsLoaded(true); if (eventQueue != null) { eventQueue.clear(); eventQueue = null; @@ -195,7 +209,6 @@ void replayEvents() { } synchronized public void replayAndDisableQueue() { - setKitsLoaded(true); replayEvents(); disableQueuing(); } @@ -461,10 +474,24 @@ public Set getSupportedKits() { } @Override - public void updateKits(JSONArray kitConfiguration) { + public KitsLoadedCallback updateKits(JSONArray kitConfiguration) { + KitsLoadedCallback kitsLoadedCallback = new KitsLoadedCallback(); if (mKitManager != null) { - mKitManager.updateKits(kitConfiguration); + // we may have initialized the KitManagerImpl but didn't have a cached config to initialize + // any kits with. In this case, we will wait until this next config update to replay + disable queueing + if (!kitsLoaded) { + mKitManager + .updateKits(kitConfiguration) + .onKitsLoaded(() -> { + setKitsLoaded(true); + kitsLoadedCallback.setKitsLoaded(); + } + ); + } else { + return mKitManager.updateKits(kitConfiguration); + } } + return kitsLoadedCallback; } @Override @@ -693,11 +720,6 @@ public String getLaunchAction() { return mAppStateManager.getLaunchAction(); } - @Override - public void replayAndDisableQueue() { - mKitFrameworkWrapper.replayAndDisableQueue(); - } - @Override public KitListener getKitListener() { return kitListener; diff --git a/android-core/src/main/java/com/mparticle/internal/KitManager.java b/android-core/src/main/java/com/mparticle/internal/KitManager.java index 052636690..67da29931 100644 --- a/android-core/src/main/java/com/mparticle/internal/KitManager.java +++ b/android-core/src/main/java/com/mparticle/internal/KitManager.java @@ -78,7 +78,7 @@ public interface KitManager { Set getSupportedKits(); - void updateKits(JSONArray jsonArray); + KitsLoadedCallback updateKits(JSONArray jsonArray); void updateDataplan(@NonNull MParticleOptions.DataplanOptions dataplanOptions); diff --git a/android-core/src/main/java/com/mparticle/internal/KitsLoadedCallback.kt b/android-core/src/main/java/com/mparticle/internal/KitsLoadedCallback.kt new file mode 100644 index 000000000..612acd0fb --- /dev/null +++ b/android-core/src/main/java/com/mparticle/internal/KitsLoadedCallback.kt @@ -0,0 +1,29 @@ +package com.mparticle.internal + +class KitsLoadedCallback { + @Volatile private var onKitsLoadedRunnable: OnKitManagerLoaded? = null + @Volatile private var loaded: Boolean = false + + fun setKitsLoaded() { + synchronized(this) { + if (!loaded) { + loaded = true + onKitsLoadedRunnable?.onKitManagerLoaded() + } + } + } + + fun onKitsLoaded(callback: OnKitManagerLoaded) { + synchronized(this) { + if (loaded) { + callback.onKitManagerLoaded() + } else { + onKitsLoadedRunnable = callback + } + } + } +} + +interface OnKitManagerLoaded { + fun onKitManagerLoaded() +} diff --git a/android-core/src/main/java/com/mparticle/internal/KitsLoadedListenerConfiguration.kt b/android-core/src/main/java/com/mparticle/internal/KitsLoadedListenerConfiguration.kt new file mode 100644 index 000000000..544fab7f5 --- /dev/null +++ b/android-core/src/main/java/com/mparticle/internal/KitsLoadedListenerConfiguration.kt @@ -0,0 +1,8 @@ +package com.mparticle.internal + +import com.mparticle.Configuration + +internal class KitsLoadedListenerConfiguration(private var kitsLoadedListener: KitsLoadedListener) : Configuration { + override fun configures() = KitFrameworkWrapper::class.java + override fun apply(kitFrameworkWrapper: KitFrameworkWrapper) = kitFrameworkWrapper.addKitsLoadedListener(kitsLoadedListener) +} diff --git a/android-core/src/main/java/com/mparticle/internal/MessageManager.java b/android-core/src/main/java/com/mparticle/internal/MessageManager.java index 13500b8ee..7b544ea81 100644 --- a/android-core/src/main/java/com/mparticle/internal/MessageManager.java +++ b/android-core/src/main/java/com/mparticle/internal/MessageManager.java @@ -830,7 +830,7 @@ public void refreshConfiguration() { } public void initConfigDelayed() { - mUploadHandler.sendEmptyMessageDelayed(UploadHandler.INIT_CONFIG, 1000); + mUploadHandler.sendEmptyMessageDelayed(UploadHandler.INIT_CONFIG, 10 * 1000); } @Override diff --git a/android-core/src/test/java/com/mparticle/MockMParticle.java b/android-core/src/test/java/com/mparticle/MockMParticle.java index b7cffa788..4f7aabf39 100644 --- a/android-core/src/test/java/com/mparticle/MockMParticle.java +++ b/android-core/src/test/java/com/mparticle/MockMParticle.java @@ -4,6 +4,7 @@ import com.mparticle.internal.AppStateManager; import com.mparticle.internal.ConfigManager; import com.mparticle.internal.KitFrameworkWrapper; +import com.mparticle.internal.KitsLoadedCallback; import com.mparticle.internal.MessageManager; import com.mparticle.media.MPMediaAPI; import com.mparticle.messaging.MPMessagingAPI; @@ -26,6 +27,9 @@ public MockMParticle() { mMessaging = Mockito.mock(MPMessagingAPI.class); mMedia = Mockito.mock(MPMediaAPI.class); mIdentityApi = new IdentityApi(new MockContext(), mAppStateManager, mMessageManager, Internal().getConfigManager(), mKitManager, OperatingSystem.ANDROID); + Mockito.when(mKitManager.updateKits(Mockito.any())).thenReturn(new KitsLoadedCallback()); } + + } diff --git a/android-core/src/test/java/com/mparticle/internal/ConfigManagerTest.java b/android-core/src/test/java/com/mparticle/internal/ConfigManagerTest.java index 817675d71..0cea29c7e 100644 --- a/android-core/src/test/java/com/mparticle/internal/ConfigManagerTest.java +++ b/android-core/src/test/java/com/mparticle/internal/ConfigManagerTest.java @@ -196,14 +196,6 @@ public void testUploadInterval() throws Exception { assertEquals(1000 * 110, manager.getUploadInterval()); } - @Test - public void testUploadIntervalNoOverridenDelayedInit() { - manager.setUploadInterval(123); - assertEquals(123 * 1000, manager.getUploadInterval()); - manager.delayedStart(); - assertEquals(123 * 1000, manager.getUploadInterval()); - } - @Test public void testGetEnvironment() throws Exception { assertEquals(MParticle.Environment.Production, manager.getEnvironment()); diff --git a/android-core/src/test/java/com/mparticle/internal/KitFrameworkWrapperTest.java b/android-core/src/test/java/com/mparticle/internal/KitFrameworkWrapperTest.java index a5a4acad5..b1550cbc7 100644 --- a/android-core/src/test/java/com/mparticle/internal/KitFrameworkWrapperTest.java +++ b/android-core/src/test/java/com/mparticle/internal/KitFrameworkWrapperTest.java @@ -47,7 +47,6 @@ public void testLoadKitLibrary() throws Exception { Mockito.mock(ReportingManager.class), mockConfigManager, Mockito.mock(AppStateManager.class), - mockBackgroundTaskHandler, true, Mockito.mock(MParticleOptions.class)); @@ -67,7 +66,6 @@ public void testDisableQueuing() throws Exception { Mockito.mock(ReportingManager.class), Mockito.mock(ConfigManager.class), Mockito.mock(AppStateManager.class), - mockBackgroundTaskHandler, true, Mockito.mock(MParticleOptions.class)); assertFalse(wrapper.getKitsLoaded()); @@ -80,7 +78,6 @@ public void testDisableQueuing() throws Exception { assertEquals("a key", wrapper.getAttributeQueue().peek().key); assertEquals("a value", wrapper.getAttributeQueue().peek().value); wrapper.disableQueuing(); - assertTrue(wrapper.getKitsLoaded()); assertNull(wrapper.getEventQueue()); assertNull(wrapper.getAttributeQueue()); } @@ -92,7 +89,6 @@ public void testReplayEvents() throws Exception { Mockito.mock(ReportingManager.class), Mockito.mock(ConfigManager.class), Mockito.mock(AppStateManager.class), - mockBackgroundTaskHandler, true, Mockito.mock(MParticleOptions.class)); Mockito.when(wrapper.mCoreCallbacks.getPushInstanceId()).thenReturn("instanceId"); @@ -164,12 +160,10 @@ public void testReplayAndDisableQueue() throws Exception { Mockito.mock(ReportingManager.class), Mockito.mock(ConfigManager.class), Mockito.mock(AppStateManager.class), - mockBackgroundTaskHandler, true, Mockito.mock(MParticleOptions.class)); wrapper.setKitsLoaded(false); wrapper.replayAndDisableQueue(); - assertTrue(wrapper.getKitsLoaded()); } @Test @@ -178,7 +172,6 @@ public void testQueueStringAttribute() throws Exception { Mockito.mock(ReportingManager.class), Mockito.mock(ConfigManager.class), Mockito.mock(AppStateManager.class), - mockBackgroundTaskHandler, true, Mockito.mock(MParticleOptions.class)); assertNull(wrapper.getAttributeQueue()); @@ -195,7 +188,6 @@ public void testQueueNullAttribute() throws Exception { Mockito.mock(ReportingManager.class), Mockito.mock(ConfigManager.class), Mockito.mock(AppStateManager.class), - mockBackgroundTaskHandler, true, Mockito.mock(MParticleOptions.class)); assertNull(wrapper.getAttributeQueue()); @@ -212,7 +204,6 @@ public void testQueueListAttribute() throws Exception { Mockito.mock(ReportingManager.class), Mockito.mock(ConfigManager.class), Mockito.mock(AppStateManager.class), - mockBackgroundTaskHandler, true, Mockito.mock(MParticleOptions.class)); assertNull(wrapper.getAttributeQueue()); @@ -229,7 +220,6 @@ public void testQueueAttributeRemoval() throws Exception { Mockito.mock(ReportingManager.class), Mockito.mock(ConfigManager.class), Mockito.mock(AppStateManager.class), - mockBackgroundTaskHandler, true, Mockito.mock(MParticleOptions.class)); assertNull(wrapper.getAttributeQueue()); @@ -246,7 +236,6 @@ public void testQueueAttributeIncrement() throws Exception { Mockito.mock(ReportingManager.class), Mockito.mock(ConfigManager.class), Mockito.mock(AppStateManager.class), - mockBackgroundTaskHandler, true, Mockito.mock(MParticleOptions.class)); assertNull(wrapper.getAttributeQueue()); @@ -263,7 +252,6 @@ public void testQueueEvent() throws Exception { Mockito.mock(ReportingManager.class), Mockito.mock(ConfigManager.class), Mockito.mock(AppStateManager.class), - mockBackgroundTaskHandler, true, Mockito.mock(MParticleOptions.class)); assertNull(wrapper.getEventQueue()); @@ -284,7 +272,6 @@ public void testSetUserAttribute() throws Exception { Mockito.mock(ReportingManager.class), Mockito.mock(ConfigManager.class), Mockito.mock(AppStateManager.class), - mockBackgroundTaskHandler, true, Mockito.mock(MParticleOptions.class)); assertNull(wrapper.getAttributeQueue()); @@ -317,7 +304,6 @@ public void testLogEvent() throws Exception { Mockito.mock(ReportingManager.class), Mockito.mock(ConfigManager.class), Mockito.mock(AppStateManager.class), - mockBackgroundTaskHandler, true, Mockito.mock(MParticleOptions.class)); assertNull(wrapper.getEventQueue()); @@ -356,7 +342,6 @@ public void testLogCommerceEvent() throws Exception { Mockito.mock(ReportingManager.class), Mockito.mock(ConfigManager.class), Mockito.mock(AppStateManager.class), - mockBackgroundTaskHandler, true, Mockito.mock(MParticleOptions.class)); assertNull(wrapper.getEventQueue()); @@ -395,7 +380,6 @@ public void testLogBaseEvent() { Mockito.mock(ReportingManager.class), Mockito.mock(ConfigManager.class), Mockito.mock(AppStateManager.class), - mockBackgroundTaskHandler, true, Mockito.mock(MParticleOptions.class)); assertNull(wrapper.getEventQueue()); @@ -433,7 +417,6 @@ public void testLogScreen() throws Exception { Mockito.mock(ReportingManager.class), Mockito.mock(ConfigManager.class), Mockito.mock(AppStateManager.class), - mockBackgroundTaskHandler, true, Mockito.mock(MParticleOptions.class)); assertNull(wrapper.getEventQueue()); @@ -472,7 +455,6 @@ public void testIsKitActive() throws Exception { Mockito.mock(ReportingManager.class), Mockito.mock(ConfigManager.class), Mockito.mock(AppStateManager.class), - mockBackgroundTaskHandler, true, Mockito.mock(MParticleOptions.class)); assertFalse(wrapper.isKitActive(0)); @@ -494,7 +476,6 @@ public void testGetSupportedKits() throws Exception { Mockito.mock(ReportingManager.class), Mockito.mock(ConfigManager.class), Mockito.mock(AppStateManager.class), - mockBackgroundTaskHandler, true, Mockito.mock(MParticleOptions.class)); assertNull(wrapper.getSupportedKits()); diff --git a/android-core/src/test/java/com/mparticle/internal/MParticleApiClientImplTest.java b/android-core/src/test/java/com/mparticle/internal/MParticleApiClientImplTest.java index 4002f5c89..04dc8e0a2 100644 --- a/android-core/src/test/java/com/mparticle/internal/MParticleApiClientImplTest.java +++ b/android-core/src/test/java/com/mparticle/internal/MParticleApiClientImplTest.java @@ -1,14 +1,17 @@ package com.mparticle.internal; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + import android.content.SharedPreferences; -import com.mparticle.identity.AliasRequest; import com.mparticle.mock.MockContext; import com.mparticle.mock.MockSharedPreferences; import com.mparticle.networking.MPConnection; import com.mparticle.networking.MPUrl; import com.mparticle.networking.MParticleBaseClientImpl; -import com.mparticle.testutils.TestingUtils; import org.json.JSONObject; import org.junit.Test; @@ -19,18 +22,10 @@ import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; -import java.io.IOException; import java.math.BigInteger; -import java.net.HttpURLConnection; import java.net.URL; import java.util.List; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - @RunWith(PowerMockRunner.class) public class MParticleApiClientImplTest { diff --git a/android-core/src/test/java/com/mparticle/internal/MockKitManager.java b/android-core/src/test/java/com/mparticle/internal/MockKitManager.java deleted file mode 100644 index 8935119ce..000000000 --- a/android-core/src/test/java/com/mparticle/internal/MockKitManager.java +++ /dev/null @@ -1,263 +0,0 @@ -package com.mparticle.internal; - -import android.app.Activity; -import android.content.Context; -import android.content.Intent; -import android.location.Location; -import android.net.Uri; -import android.os.Bundle; - -import com.mparticle.AttributionResult; -import com.mparticle.BaseEvent; -import com.mparticle.MPEvent; -import com.mparticle.MParticle; -import com.mparticle.MParticleOptions; -import com.mparticle.commerce.CommerceEvent; -import com.mparticle.consent.ConsentState; -import com.mparticle.identity.IdentityApiRequest; -import com.mparticle.identity.MParticleUser; - -import org.json.JSONArray; - -import java.lang.ref.WeakReference; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; - -public class MockKitManager implements KitManager { - public MockKitManager(Context context) { - super(); - } - - - @Override - public WeakReference getCurrentActivity() { - return null; - } - - @Override - public void logEvent(BaseEvent event) { - - } - - @Override - public void logScreen(MPEvent screenEvent) { - - } - - @Override - public void logBatch(String jsonObject) { - - } - - @Override - public void leaveBreadcrumb(String breadcrumb) { - - } - - @Override - public void logError(String message, Map eventData) { - - } - - @Override - public void logNetworkPerformance(String url, long startTime, String method, long length, long bytesSent, long bytesReceived, String requestString, int responseCode) { - - } - - @Override - public void logException(Exception exception, Map eventData, String message) { - - } - - @Override - public void setLocation(Location location) { - - } - - @Override - public void logout() { - - } - - @Override - public void setUserAttribute(String key, String value, long mpid) { - - } - - @Override - public void setUserAttributeList(String key, List value, long mpid) { - - } - - @Override - public void removeUserAttribute(String key, long mpid) { - - } - - @Override - public void setUserTag(String tag, long mpid) { - - } - - @Override - public void incrementUserAttribute(String key, int increasedBy, String value, long mpid) { - - } - - @Override - public void onConsentStateUpdated(ConsentState oldState, ConsentState newState, long mpid) { - - } - - @Override - public void setUserIdentity(String id, MParticle.IdentityType identityType) { - - } - - @Override - public void removeUserIdentity(MParticle.IdentityType id) { - - } - - @Override - public void setOptOut(boolean optOutStatus) { - - } - - @Override - public Uri getSurveyUrl(int serviceProviderId, Map userAttributes, Map> userAttributeLists) { - return null; - } - - @Override - public boolean onMessageReceived(Context context, Intent intent) { - return false; - } - - @Override - public boolean onPushRegistration(String instanceId, String senderId) { - return false; - } - - @Override - public boolean isKitActive(int kitId) { - return false; - } - - @Override - public Object getKitInstance(int kitId) { - return null; - } - - @Override - public Set getSupportedKits() { - return null; - } - - @Override - public void updateKits(JSONArray jsonArray) { - - } - - @Override - public void updateDataplan(MParticleOptions.DataplanOptions dataplanOptions) { - - } - - @Override - public String getActiveModuleIds() { - return "this is a fake module id string"; - } - - @Override - public void onActivityCreated(Activity activity, Bundle savedInstanceState) { - - } - - @Override - public void onActivityStarted(Activity activity) { - - } - - @Override - public void onActivityResumed(Activity activity) { - - } - - @Override - public void onActivityPaused(Activity activity) { - - } - - @Override - public void onActivityStopped(Activity activity) { - - } - - @Override - public void onActivitySaveInstanceState(Activity activity, Bundle outState) { - - } - - @Override - public void onActivityDestroyed(Activity activity) { - - } - - @Override - public void onSessionEnd() { - - } - - @Override - public void onSessionStart() { - - } - - @Override - public void installReferrerUpdated() { - - } - - @Override - public void onApplicationForeground() { - - } - - @Override - public void onApplicationBackground() { - - } - - @Override - public Map getAttributionResults() { - return new TreeMap(); - } - - @Override - public void onIdentifyCompleted(MParticleUser user, IdentityApiRequest request) { - - } - - @Override - public void onLoginCompleted(MParticleUser user, IdentityApiRequest request) { - - } - - @Override - public void onLogoutCompleted(MParticleUser user, IdentityApiRequest request) { - - } - - @Override - public void onModifyCompleted(MParticleUser user, IdentityApiRequest request) { - - } - - @Override - public void reset() { - - } -} diff --git a/android-kit-base/src/androidTest/java/com/mparticle/kits/KnownUserKitsLifecycleTest.java b/android-kit-base/src/androidTest/java/com/mparticle/kits/KnownUserKitsLifecycleTest.java index ccec219b6..790913306 100644 --- a/android-kit-base/src/androidTest/java/com/mparticle/kits/KnownUserKitsLifecycleTest.java +++ b/android-kit-base/src/androidTest/java/com/mparticle/kits/KnownUserKitsLifecycleTest.java @@ -82,13 +82,10 @@ public void testExcludeUnknownUsers() throws InterruptedException { private void waitForNewIdentityKitStart(final long mpid) throws InterruptedException { final CountDownLatch latch = new MPLatch(1); - AccessUtils.getKitManager().addKitsLoadedListener(new KitManagerImpl.KitsLoadedListener() { - @Override - public void onKitsLoaded(Map kits, Map previousKits, JSONArray kitConfigs) { - if (kitConfigs != null && kitConfigs.length() == 3) { - latch.countDown(); - Logger.error("kits started: " + kits.size()); - } + AccessUtils.getKitManager().addKitsLoadedListener((kits, previousKits, kitConfigs) -> { + if (kitConfigs != null && kitConfigs.size() == 3) { + latch.countDown(); + Logger.error("kits started: " + kits.size()); } }); latch.await(3, TimeUnit.SECONDS); diff --git a/android-kit-base/src/androidTest/kotlin/com/mparticle/kits/BaseKitOptionsTest.kt b/android-kit-base/src/androidTest/kotlin/com/mparticle/kits/BaseKitOptionsTest.kt index 4da2c775c..b78e62b37 100644 --- a/android-kit-base/src/androidTest/kotlin/com/mparticle/kits/BaseKitOptionsTest.kt +++ b/android-kit-base/src/androidTest/kotlin/com/mparticle/kits/BaseKitOptionsTest.kt @@ -1,8 +1,11 @@ package com.mparticle.kits import com.mparticle.AccessUtils +import com.mparticle.Configuration +import com.mparticle.MParticle import com.mparticle.MParticleOptions import com.mparticle.testutils.BaseCleanInstallEachTest +import com.mparticle.testutils.MPLatch import org.json.JSONArray import org.json.JSONObject @@ -10,8 +13,23 @@ open class BaseKitOptionsTest : BaseCleanInstallEachTest() { override fun startMParticle(optionsBuilder: MParticleOptions.Builder) { AccessUtils.setCredentialsIfEmpty(optionsBuilder) + val kitsLoadedLatch = MPLatch(1) + var kitCount = 0 + val kitsLoadedListener = object : Configuration { + override fun configures() = KitManagerImpl::class.java - val options: MParticleOptions = optionsBuilder.build() + override fun apply(kitManager: KitManagerImpl) { + kitManager.addKitsLoadedListener { kits, previousKits, kitConfigs -> + if (kitConfigs.size == kitCount) { + kitsLoadedLatch.countDown() + } + } + } + } + + val options: MParticleOptions = optionsBuilder + .configuration(kitsLoadedListener) + .build() val kitOptions = options.getConfiguration(KitOptions::class.java) ?: KitOptions() val configuredKitOptions = options.getConfiguration(ConfiguredKitOptions::class.java) ?: ConfiguredKitOptions() @@ -21,17 +39,38 @@ open class BaseKitOptionsTest : BaseCleanInstallEachTest() { (kitOptions.kits + configuredKitOptions.kits).forEach { id, clazz -> kitConfigurations[id] = JSONObject().put("id", id) } - configuredKitOptions.configurations.forEach { id, config -> + configuredKitOptions.testingConfiguration.forEach { id, config -> kitConfigurations[id] = config } kitConfigurations.values + .filterNotNull() .fold(JSONArray()) { init, next -> init.apply { put(next) } } .let { + kitCount = it.length() JSONObject().put("eks", it) } .let { mServer.setupConfigResponse(it.toString()) } + if (kitConfigurations.isEmpty()) { + kitsLoadedLatch.countDown() + } super.startMParticle(optionsBuilder) + kitsLoadedLatch.await() + } + + protected fun waitForKitToStart(kitId: Int) { + val latch = MPLatch(1) + // wait for kit to start/reload + com.mparticle.internal.AccessUtils.getKitManager().addKitsLoadedListener { kits, previousKits, kitConfigs -> + if (kits.containsKey(kitId)) { + latch.countDown() + } + } + // check if the kit has already been started and short-circut if it has + if (MParticle.getInstance()?.isKitActive(kitId) == true) { + latch.countDown() + } + latch.await() } } diff --git a/android-kit-base/src/androidTest/kotlin/com/mparticle/kits/ConfiguredKitOptions.kt b/android-kit-base/src/androidTest/kotlin/com/mparticle/kits/ConfiguredKitOptions.kt index 040134936..103c41383 100644 --- a/android-kit-base/src/androidTest/kotlin/com/mparticle/kits/ConfiguredKitOptions.kt +++ b/android-kit-base/src/androidTest/kotlin/com/mparticle/kits/ConfiguredKitOptions.kt @@ -3,14 +3,14 @@ package com.mparticle.kits import org.json.JSONObject class ConfiguredKitOptions : KitOptions() { - val configurations = mutableMapOf() + val testingConfiguration = mutableMapOf() override fun addKit(kitId: Int, type: Class): ConfiguredKitOptions { return addKit(kitId, type, JSONObject().put("id", kitId)) } fun addKit(kitId: Int, type: Class, config: JSONObject?): ConfiguredKitOptions { - configurations[kitId] = config?.put("id", kitId) ?: JSONObject() + testingConfiguration[kitId] = config?.put("id", kitId) super.addKit(kitId, type) return this } diff --git a/android-kit-base/src/androidTest/kotlin/com/mparticle/kits/UpdateConfigTest.kt b/android-kit-base/src/androidTest/kotlin/com/mparticle/kits/UpdateConfigTest.kt index 18385fda0..33b3bda04 100644 --- a/android-kit-base/src/androidTest/kotlin/com/mparticle/kits/UpdateConfigTest.kt +++ b/android-kit-base/src/androidTest/kotlin/com/mparticle/kits/UpdateConfigTest.kt @@ -89,8 +89,6 @@ class UpdateConfigTest : BaseKitOptionsTest() { startMParticle(it) } - waitForKitToStart(1) - mServer.setupConfigResponse( JSONObject() .put( @@ -112,19 +110,4 @@ class UpdateConfigTest : BaseKitOptionsTest() { AccessUtils.forceFetchConfig() latch.await() } - - private fun waitForKitToStart(kitId: Int) { - val latch = MPLatch(1) - // wait for kit to start/reload - AccessUtils.getKitManager().addKitsLoadedListener { kits, previousKits, kitConfigs -> - if (kits.containsKey(kitId)) { - latch.countDown() - } - } - // check if the kit has already been started and short-circut if it has - if (MParticle.getInstance()?.isKitActive(kitId) == true) { - latch.countDown() - } - latch.await() - } } diff --git a/android-kit-base/src/main/java/com/mparticle/kits/KitIntegration.java b/android-kit-base/src/main/java/com/mparticle/kits/KitIntegration.java index 3e9a643f4..b007f8319 100644 --- a/android-kit-base/src/main/java/com/mparticle/kits/KitIntegration.java +++ b/android-kit-base/src/main/java/com/mparticle/kits/KitIntegration.java @@ -335,7 +335,7 @@ public void onSettingsUpdated(Map settings) { * Queues and groups network requests on the MParticle Core network handler */ public void executeNetworkRequest(Runnable runnable) { - getKitManager().executeNetworkRequest(runnable); + getKitManager().runOnBackgroundThread(runnable); } /** diff --git a/android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java b/android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java index d68691c0e..7f201dae1 100644 --- a/android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java +++ b/android-kit-base/src/main/java/com/mparticle/kits/KitManagerImpl.java @@ -7,8 +7,10 @@ import android.net.Uri; import android.os.Bundle; import android.os.Handler; +import android.os.HandlerThread; import android.os.Looper; +import androidx.annotation.MainThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -26,7 +28,7 @@ import com.mparticle.identity.IdentityApiRequest; import com.mparticle.identity.IdentityStateListener; import com.mparticle.identity.MParticleUser; -import com.mparticle.internal.BackgroundTaskHandler; +import com.mparticle.internal.KitsLoadedCallback; import com.mparticle.internal.CoreCallbacks; import com.mparticle.internal.KitManager; import com.mparticle.internal.Logger; @@ -51,14 +53,21 @@ import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; - public class KitManagerImpl implements KitManager, AttributionListener, UserAttributeListener, IdentityStateListener { + + private static HandlerThread kitHandlerThread; + static { + kitHandlerThread = new HandlerThread("mParticle_kit_thread"); + kitHandlerThread.start(); + } + private final ReportingManager mReportingManager; protected final CoreCallbacks mCoreCallbacks; - private final BackgroundTaskHandler mBackgroundTaskHandler; + private Handler mBackgroundTaskHandler; KitIntegrationFactory mKitIntegrationFactory; private DataplanFilter mDataplanFilter = DataplanFilterImpl.EMPTY; private KitOptions mKitOptions; + private volatile List kitConfigurations = new ArrayList<>(); private static final String RESERVED_KEY_LTV = "$Amount"; private static final String METHOD_NAME = "$MethodName"; @@ -71,11 +80,10 @@ public class KitManagerImpl implements KitManager, AttributionListener, UserAttr ConcurrentHashMap providers = new ConcurrentHashMap(); private final Context mContext; - public KitManagerImpl(Context context, ReportingManager reportingManager, CoreCallbacks coreCallbacks, BackgroundTaskHandler backgroundTaskHandler, MParticleOptions options) { + public KitManagerImpl(Context context, ReportingManager reportingManager, CoreCallbacks coreCallbacks, MParticleOptions options) { mContext = context; mReportingManager = reportingManager; mCoreCallbacks = coreCallbacks; - mBackgroundTaskHandler = backgroundTaskHandler; mKitIntegrationFactory = new KitIntegrationFactory(); MParticle instance = MParticle.getInstance(); if (instance != null) { @@ -140,30 +148,23 @@ void clearIntegrationAttributes(KitIntegration kitIntegration) { setIntegrationAttributes(kitIntegration, null); } - class UpdateKitRunnable implements Runnable { - - private final JSONArray kitConfigs; - - UpdateKitRunnable(JSONArray kitConfigs) { - super(); - this.kitConfigs = kitConfigs; - } - - @Override - public void run() { - configureKits(kitConfigs); - } - + @Override + public KitsLoadedCallback updateKits(final JSONArray kitConfigs) { + KitsLoadedCallback callback = new KitsLoadedCallback(); + runOnBackgroundThread(() -> { + kitConfigurations = parseKitConfigurations(kitConfigs); + runOnMainThread(() -> { + configureKits(kitConfigurations); + callback.setKitsLoaded(); + }); + } + ); + return callback; } - @Override - public void updateKits(final JSONArray kitConfigs) { - if (Looper.getMainLooper() != Looper.myLooper()) { - Runnable runnable = new UpdateKitRunnable(kitConfigs); - new Handler(Looper.getMainLooper()).post(runnable); - } else { - configureKits(kitConfigs); - } + @MainThread + public void reloadKits() { + configureKits(kitConfigurations); } @Override @@ -188,7 +189,10 @@ public void updateDataplan(@Nullable MParticleOptions.DataplanOptions dataplanOp *

* Note: This method is meant to always be run on the main thread. */ - protected void configureKits(JSONArray kitConfigs) { + protected synchronized void configureKits(@NonNull List kitConfigurations) { + if (kitConfigurations == null) { + kitConfigurations = new ArrayList<>(); + } MParticle instance = MParticle.getInstance(); if (instance == null) { //if MParticle has been dereferenced, abandon ship. This will run again when it is restarted @@ -197,13 +201,11 @@ protected void configureKits(JSONArray kitConfigs) { MParticleUser user = instance.Identity().getCurrentUser(); HashSet activeIds = new HashSet(); HashMap previousKits = new HashMap<>(providers); - if (kitConfigs != null) { - for (int i = 0; i < kitConfigs.length(); i++) { + + if (kitConfigurations != null) { + for (KitConfiguration configuration: kitConfigurations) { try { - JSONObject current = kitConfigs.getJSONObject(i); - KitConfiguration configuration = createKitConfiguration(current); int currentModuleID = configuration.getKitId(); - mCoreCallbacks.getKitListener().kitConfigReceived(currentModuleID, configuration.toString()); if (configuration.shouldExcludeUser(user)) { mCoreCallbacks.getKitListener().kitExcluded(currentModuleID, "User was required to be known, but was not."); continue; @@ -234,12 +236,8 @@ protected void configureKits(JSONArray kitConfigs) { activeKit.onSettingsUpdated(configuration.getSettings()); } } catch (Exception e) { - try { - mCoreCallbacks.getKitListener().kitExcluded(kitConfigs.getJSONObject(i).optInt("id", -1), "exception while starting. Exception: " + e.getMessage()); - } catch (JSONException e1) { - mCoreCallbacks.getKitListener().kitExcluded(-1, "exception while starting. Exception: " + e.getMessage()); - } - Logger.error("Exception while starting kit: " + e.getMessage()); + mCoreCallbacks.getKitListener().kitExcluded(configuration.getKitId(), "exception while starting. Exception: " + e.getMessage()); + Logger.error("Exception while starting kit " + configuration.getKitId() + ": " + e.getMessage()); } } } @@ -260,8 +258,7 @@ protected void configureKits(JSONArray kitConfigs) { getContext().sendBroadcast(intent); } } - onKitsLoaded(new HashMap<>(providers), previousKits, kitConfigs); - mCoreCallbacks.replayAndDisableQueue(); + onKitsLoaded(new HashMap<>(providers), previousKits, new ArrayList<>(kitConfigurations)); } private void initializeKit(KitIntegration activeKit) { @@ -406,7 +403,7 @@ public void setOptOut(boolean optOutStatus) { Logger.warning("Failed to call setOptOut for kit: " + provider.getName() + ": " + e.getMessage()); } } - updateKits(mCoreCallbacks.getLatestKitConfiguration()); + reloadKits(); } @Override @@ -1226,7 +1223,7 @@ public void installReferrerUpdated() { @Override public void onUserIdentified(MParticleUser mParticleUser, MParticleUser previousUser) { //due to consent forwarding rules we need to re-verify kits whenever the user changes - updateKits(mCoreCallbacks.getLatestKitConfiguration()); + reloadKits(); for (KitIntegration provider : providers.values()) { try { if (provider instanceof KitIntegration.IdentityListener && !provider.isDisabled()) { @@ -1236,6 +1233,7 @@ public void onUserIdentified(MParticleUser mParticleUser, MParticleUser previous Logger.warning("Failed to call onUserIdentified for kit: " + provider.getName() + ": " + e.getMessage()); } } + mParticleUser.getUserAttributes(this); } @@ -1294,29 +1292,16 @@ public void onModifyCompleted(MParticleUser mParticleUser, IdentityApiRequest id @Override public void onConsentStateUpdated(final ConsentState oldState, final ConsentState newState, final long mpid) { //Due to consent forwarding rules we need to re-initialize kits whenever the user changes. - updateKits(mCoreCallbacks.getLatestKitConfiguration()); - Runnable runnable = new Runnable() { - @Override - public void run() { - for (KitIntegration provider : providers.values()) { - if (provider instanceof KitIntegration.UserAttributeListener && !provider.isDisabled()) { - try { - ((KitIntegration.UserAttributeListener) provider).onConsentStateUpdated(oldState, newState, FilteredMParticleUser.getInstance(mpid, provider)); - } catch (Exception e) { - Logger.warning("Failed to call onConsentStateUpdated for kit: " + provider.getName() + ": " + e.getMessage()); - } - } + reloadKits(); + for (KitIntegration provider : providers.values()) { + if (provider instanceof KitIntegration.UserAttributeListener && !provider.isDisabled()) { + try { + ((KitIntegration.UserAttributeListener) provider).onConsentStateUpdated(oldState, newState, FilteredMParticleUser.getInstance(mpid, provider)); + } catch (Exception e) { + Logger.warning("Failed to call onConsentStateUpdated for kit: " + provider.getName() + ": " + e.getMessage()); } } - }; - //This needs to be run on the main thread, after kit configuration/consent forwarding rules, which also happen on the main thread. - if (Looper.getMainLooper() != Looper.myLooper()) { - Handler handler = new Handler(getContext().getMainLooper()); - handler.post(runnable); - } else { - runnable.run(); } - } @Override @@ -1330,8 +1315,19 @@ public void reset() { } } - public void executeNetworkRequest(Runnable runnable) { - mBackgroundTaskHandler.executeNetworkRequest(runnable); + public void runOnBackgroundThread(Runnable runnable) { + if (mBackgroundTaskHandler == null) { + mBackgroundTaskHandler = new Handler(kitHandlerThread.getLooper()); + } + mBackgroundTaskHandler.post(runnable); + } + + public void runOnMainThread(Runnable runnable) { + if (Looper.myLooper() == Looper.getMainLooper()) { + runnable.run(); + } else { + new Handler(Looper.getMainLooper()).post(runnable); + } } public boolean isPushEnabled() { @@ -1391,18 +1387,42 @@ private void initializeKitIntegrationFactory() { } } + private List parseKitConfigurations(JSONArray kitConfigs) { + List configurations = new ArrayList<>(); + if (kitConfigs == null) { + kitConfigs = new JSONArray(); + } + for (int i = 0; i < kitConfigs.length(); i++) { + JSONObject kitConfig = null; + try { + kitConfig = kitConfigs.getJSONObject(i); + } catch (JSONException e) { + Logger.error(e, "Malformed Kit configuration"); + } + if (kitConfig != null) { + try { + configurations.add(createKitConfiguration(kitConfig)); + } catch (JSONException e) { + int kitId = kitConfig.optInt("id", -1); + mCoreCallbacks.getKitListener().kitExcluded(kitId, "exception while starting. Exception: " + e.getMessage()); + Logger.error("Exception while starting kit: " + kitId + ": " + e.getMessage()); + } + } + } + return configurations; + } + public void addKitsLoadedListener(KitsLoadedListener kitsLoadedListener) { - kitsLoadedListeners.add(kitsLoadedListener); + kitsLoadedListeners.add((KitsLoadedListener) kitsLoadedListener); } - private void onKitsLoaded(Map kits, Map previousKits, JSONArray kitConfigs) { + private void onKitsLoaded(Map kits, Map previousKits, List kitConfigs) { for(KitsLoadedListener listener: kitsLoadedListeners) { listener.onKitsLoaded(kits, previousKits, kitConfigs); } } - public interface KitsLoadedListener { - void onKitsLoaded(Map kits, Map previousKits, JSONArray kitConfigs); + interface KitsLoadedListener { + void onKitsLoaded(Map kits, Map previousKits, List kitConfigs); } - } diff --git a/android-kit-base/src/test/java/com/mparticle/kits/KitManagerImplTest.java b/android-kit-base/src/test/java/com/mparticle/kits/KitManagerImplTest.java index 2aaa7a8cb..81b4c8119 100644 --- a/android-kit-base/src/test/java/com/mparticle/kits/KitManagerImplTest.java +++ b/android-kit-base/src/test/java/com/mparticle/kits/KitManagerImplTest.java @@ -1,5 +1,9 @@ package com.mparticle.kits; +import static junit.framework.TestCase.assertEquals; +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertTrue; + import com.mparticle.BaseEvent; import com.mparticle.MPEvent; import com.mparticle.MParticle; @@ -9,12 +13,11 @@ import com.mparticle.consent.GDPRConsent; import com.mparticle.identity.IdentityApi; import com.mparticle.identity.MParticleUser; +import com.mparticle.internal.BackgroundTaskHandler; import com.mparticle.internal.CoreCallbacks; -import com.mparticle.internal.KitManager; +import com.mparticle.mock.MockKitConfiguration; import com.mparticle.mock.MockKitManagerImpl; import com.mparticle.mock.MockMParticle; -import com.mparticle.internal.BackgroundTaskHandler; -import com.mparticle.mock.MockKitConfiguration; import com.mparticle.testutils.TestingUtils; import junit.framework.Assert; @@ -31,10 +34,6 @@ import java.util.List; import java.util.Map; -import static junit.framework.TestCase.assertEquals; -import static junit.framework.TestCase.assertFalse; -import static junit.framework.TestCase.assertTrue; - public class KitManagerImplTest { BackgroundTaskHandler mockBackgroundTaskHandler = new BackgroundTaskHandler(){ @@ -52,7 +51,7 @@ public void before() { MockMParticle instance = new MockMParticle(); instance.setIdentityApi(mockIdentity); MParticle.setInstance(instance); - } + } @Test public void testSetKitFactory() { @@ -76,12 +75,12 @@ public void testShouldEnableKit() throws Exception { KitIntegrationFactory factory = Mockito.mock(KitIntegrationFactory.class); manager.setKitFactory(factory); Mockito.when(factory.isSupported(Mockito.anyInt())).thenReturn(true); - KitIntegration mockKit = Mockito.mock(KitIntegration.class); + KitIntegration mockKit = Mockito.mock(KitIntegration.class); Mockito.when(mockKit.getConfiguration()).thenReturn(Mockito.mock(KitConfiguration.class)); Mockito.when(factory.createInstance(Mockito.any(KitManagerImpl.class), Mockito.any(KitConfiguration.class))).thenReturn(mockKit); - manager.configureKits(kitConfiguration); - Assert.assertEquals(2, manager.providers.size()); - Assert.assertEquals(mockKit, manager.providers.values().iterator().next()); + manager.updateKits(kitConfiguration); + assertEquals(2, manager.providers.size()); + assertEquals(mockKit, manager.providers.values().iterator().next()); } @Test @@ -103,9 +102,8 @@ public void testShouldNotEnableKitBasedOnConsent() throws Exception { KitIntegration mockKit = Mockito.mock(KitIntegration.class); Mockito.when(mockKit.getConfiguration()).thenReturn(Mockito.mock(KitConfiguration.class)); Mockito.when(factory.createInstance(Mockito.any(KitManagerImpl.class), Mockito.any(KitConfiguration.class))).thenReturn(mockKit); - manager.configureKits(kitConfiguration); + manager.updateKits(kitConfiguration); Assert.assertEquals(1, manager.providers.size()); - } @Test @@ -126,9 +124,8 @@ public void testShouldEnableKitBasedOnConsent() throws Exception { KitIntegration mockKit = Mockito.mock(KitIntegration.class); Mockito.when(mockKit.getConfiguration()).thenReturn(Mockito.mock(KitConfiguration.class)); Mockito.when(factory.createInstance(Mockito.any(KitManagerImpl.class), Mockito.any(KitConfiguration.class))).thenReturn(mockKit); - manager.configureKits(kitConfiguration); - Assert.assertEquals(2, manager.providers.size()); - + manager.updateKits(kitConfiguration); + assertEquals(2, manager.providers.size()); } @Test @@ -146,22 +143,18 @@ public void testShouldDisableActiveKitBasedOnConsent() throws Exception { KitIntegrationFactory factory = Mockito.mock(KitIntegrationFactory.class); manager.setKitFactory(factory); Mockito.when(factory.isSupported(Mockito.anyInt())).thenReturn(true); - KitIntegration mockKit = Mockito.mock(KitIntegration.class); + KitIntegration mockKit = Mockito.mock(KitIntegration.class); Mockito.when(mockKit.getConfiguration()).thenReturn(Mockito.mock(KitConfiguration.class)); Mockito.when(factory.createInstance(Mockito.any(KitManagerImpl.class), Mockito.any(KitConfiguration.class))).thenReturn(mockKit); - manager.configureKits(kitConfiguration); - Assert.assertEquals(2, manager.providers.size()); - - state = ConsentState.builder().build(); - Mockito.when(mockUser.getConsentState()).thenReturn(state); - - manager.configureKits(kitConfiguration); - Assert.assertEquals(1, manager.providers.size()); - + manager.updateKits(kitConfiguration); + assertEquals(2, manager.providers.size()); + Mockito.when(mockUser.getConsentState()).thenReturn(ConsentState.builder().build()); + manager.updateKits(kitConfiguration); + assertEquals(1, manager.providers.size()); } @Test - public void testShouldEnableKitBasedOnActiveUser() throws JSONException, ClassNotFoundException { + public void testShouldEnableKitBasedOnActiveUser() throws Exception { MParticleUser mockUser = Mockito.mock(MParticleUser.class); Mockito.when(mockIdentity.getCurrentUser()).thenReturn(mockUser); Mockito.when(mockUser.isLoggedIn()).thenReturn(true); @@ -181,8 +174,8 @@ public void testShouldEnableKitBasedOnActiveUser() throws JSONException, ClassNo KitIntegration mockKit = Mockito.mock(KitIntegration.class); Mockito.when(mockKit.getConfiguration()).thenReturn(Mockito.mock(KitConfiguration.class)); Mockito.when(factory.createInstance(Mockito.any(KitManagerImpl.class), Mockito.any(KitConfiguration.class))).thenReturn(mockKit); - manager.configureKits(kitConfiguration); - Assert.assertEquals(3, manager.providers.size()); + manager.updateKits(kitConfiguration); + assertEquals(3, manager.providers.size()); } @Test @@ -202,28 +195,23 @@ public void testShouldNotEnableKitBasedOnActiveUser() throws JSONException, Clas KitIntegrationFactory factory = Mockito.mock(KitIntegrationFactory.class); manager.setKitFactory(factory); Mockito.when(factory.isSupported(Mockito.anyInt())).thenReturn(true); - KitIntegration mockKit = Mockito.mock(KitIntegration.class); + KitIntegration mockKit = Mockito.mock(KitIntegration.class); Mockito.when(mockKit.getConfiguration()).thenReturn(Mockito.mock(KitConfiguration.class)); Mockito.when(factory.createInstance(Mockito.any(KitManagerImpl.class), Mockito.any(KitConfiguration.class))).thenReturn(mockKit); - manager.configureKits(kitConfiguration); - Assert.assertEquals(1, manager.providers.size()); + manager.updateKits(kitConfiguration); + assertEquals(1, manager.providers.size()); assertTrue(manager.isKitActive(2)); assertFalse(manager.isKitActive(1)); assertFalse(manager.isKitActive(3)); } @Test - public void testShouldEnableDisabledKitBasedOnActiveUser() throws JSONException, ClassNotFoundException { + public void testShouldEnableDisabledKitBasedOnActiveUser() throws JSONException, ClassNotFoundException, InterruptedException { MParticleUser mockUser = Mockito.mock(MParticleUser.class); Mockito.when(mockIdentity.getCurrentUser()).thenReturn(mockUser); Mockito.when(mockUser.isLoggedIn()).thenReturn(false); - KitManagerImpl manager = new MockKitManagerImpl() { - @Override - public void updateKits(JSONArray kitConfigs) { - configureKits(kitConfigs); - } - }; + KitManagerImpl manager = new MockKitManagerImpl(); ConsentState state = ConsentState.builder() .addGDPRConsentState("Blah", GDPRConsent.builder(true).build()) .build(); @@ -238,7 +226,7 @@ public void updateKits(JSONArray kitConfigs) { KitIntegration mockKit = Mockito.mock(KitIntegration.class); Mockito.when(mockKit.getConfiguration()).thenReturn(Mockito.mock(KitConfiguration.class)); Mockito.when(factory.createInstance(Mockito.any(KitManagerImpl.class), Mockito.any(KitConfiguration.class))).thenReturn(mockKit); - manager.configureKits(kitConfiguration); + manager.updateKits(kitConfiguration); Assert.assertEquals(0, manager.providers.size()); Mockito.when(mockUser.isLoggedIn()).thenReturn(true); @@ -254,12 +242,7 @@ public void testShouldDisableEnabledKitBasedOnActiveUser() throws JSONException, Mockito.when(mockUser.isLoggedIn()).thenReturn(true); CoreCallbacks mockCoreCallbacks = Mockito.mock(CoreCallbacks.class); - KitManagerImpl manager = new MockKitManagerImpl() { - @Override - public void updateKits(JSONArray kitConfigs) { - configureKits(kitConfigs); - } - }; + KitManagerImpl manager = new MockKitManagerImpl(); ConsentState state = ConsentState.builder() .addGDPRConsentState("Blah", GDPRConsent.builder(true).build()) .build(); @@ -274,7 +257,7 @@ public void updateKits(JSONArray kitConfigs) { KitIntegration mockKit = Mockito.mock(KitIntegration.class); Mockito.when(mockKit.getConfiguration()).thenReturn(Mockito.mock(KitConfiguration.class)); Mockito.when(factory.createInstance(Mockito.any(KitManagerImpl.class), Mockito.any(KitConfiguration.class))).thenReturn(mockKit); - manager.configureKits(kitConfiguration); + manager.updateKits(kitConfiguration); Assert.assertEquals(3, manager.providers.size()); Mockito.when(mockUser.isLoggedIn()).thenReturn(false); @@ -393,12 +376,7 @@ public void testShouldEnableKitOnOptIn() throws Exception { ConsentState state = ConsentState.builder().build(); Mockito.when(mockUser.getConsentState()).thenReturn(state); Mockito.when(mockIdentity.getCurrentUser()).thenReturn(mockUser); - KitManagerImpl manager = new MockKitManagerImpl() { - @Override - public void updateKits(JSONArray kitConfigs) { - configureKits(kitConfigs); - } - }; + KitManagerImpl manager = new MockKitManagerImpl(); JSONArray kitConfiguration = new JSONArray(); kitConfiguration.put(new JSONObject("{\"id\":1}")); kitConfiguration.put(new JSONObject("{\"id\":2}")); @@ -413,7 +391,7 @@ public void updateKits(JSONArray kitConfigs) { Mockito.when(factory.createInstance(Mockito.any(KitManagerImpl.class), Mockito.any(KitConfiguration.class))).thenReturn(mockKit); manager.setOptOut(true); - manager.configureKits(kitConfiguration); + manager.updateKits(kitConfiguration); Assert.assertEquals(0, manager.providers.size()); Mockito.when(mockKit.isDisabled()).thenReturn(false); manager.setOptOut(false); diff --git a/testutils/build.gradle b/testutils/build.gradle index 7c7610e5b..d4b498d5c 100644 --- a/testutils/build.gradle +++ b/testutils/build.gradle @@ -53,8 +53,6 @@ dependencies { api 'androidx.annotation:annotation:1.1.0' api 'androidx.test.ext:junit:1.1.3' api 'androidx.test:rules:1.4.0' - api 'org.apache.httpcomponents:httpclient-android:4.3.5.1' - api 'org.slf4j:slf4j-api:1.7.25' androidTestCompileOnly project(':android-core') diff --git a/testutils/src/main/java/com/mparticle/internal/AccessUtils.java b/testutils/src/main/java/com/mparticle/internal/AccessUtils.java index 30839fea8..727df127b 100644 --- a/testutils/src/main/java/com/mparticle/internal/AccessUtils.java +++ b/testutils/src/main/java/com/mparticle/internal/AccessUtils.java @@ -167,7 +167,7 @@ public void run() { kitManagerLoadedLatch.await(); final CountDownLatch kitsLoadedLatch = new MPLatch(1); kitFrameworkWrapper.setKitsLoaded(false); - KitFrameworkWrapper.addKitsLoadedListener(new KitsLoadedListener() { + kitFrameworkWrapper.addKitsLoadedListener(new KitsLoadedListener() { @Override public void onKitsLoaded() { kitsLoadedLatch.countDown(); diff --git a/testutils/src/main/java/com/mparticle/mock/MockCoreCallbacks.java b/testutils/src/main/java/com/mparticle/mock/MockCoreCallbacks.java index 0472a3b69..794b2c411 100644 --- a/testutils/src/main/java/com/mparticle/mock/MockCoreCallbacks.java +++ b/testutils/src/main/java/com/mparticle/mock/MockCoreCallbacks.java @@ -78,9 +78,6 @@ public String getLaunchAction() { return null; } - @Override - public void replayAndDisableQueue() { } - @Override public KitListener getKitListener() { return KitListener.EMPTY; diff --git a/testutils/src/main/java/com/mparticle/mock/MockKitManagerImpl.java b/testutils/src/main/java/com/mparticle/mock/MockKitManagerImpl.java index 1a6beb2be..74b4de394 100644 --- a/testutils/src/main/java/com/mparticle/mock/MockKitManagerImpl.java +++ b/testutils/src/main/java/com/mparticle/mock/MockKitManagerImpl.java @@ -8,7 +8,9 @@ import com.mparticle.internal.ReportingManager; import com.mparticle.kits.KitConfiguration; import com.mparticle.kits.KitManagerImpl; +import com.mparticle.testutils.MPLatch; +import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.mockito.Mockito; @@ -21,12 +23,7 @@ public MockKitManagerImpl() { } public MockKitManagerImpl(Context context, ReportingManager reportingManager, CoreCallbacks coreCallbacks) { - super(context, reportingManager, coreCallbacks, new BackgroundTaskHandler() { - @Override - public void executeNetworkRequest(Runnable runnable) { - - } - }, Mockito.mock(MParticleOptions.class)); + super(context, reportingManager, coreCallbacks, Mockito.mock(MParticleOptions.class)); } @Override @@ -38,4 +35,14 @@ protected KitConfiguration createKitConfiguration(JSONObject configuration) thro public int getUserBucket() { return 50; } + + @Override + public void runOnBackgroundThread(Runnable runnable) { + runnable.run(); + } + + @Override + public void runOnMainThread(Runnable runnable) { + runnable.run(); + } } diff --git a/testutils/src/main/java/com/mparticle/testutils/BaseAbstractTest.java b/testutils/src/main/java/com/mparticle/testutils/BaseAbstractTest.java index b0f817562..718588be6 100644 --- a/testutils/src/main/java/com/mparticle/testutils/BaseAbstractTest.java +++ b/testutils/src/main/java/com/mparticle/testutils/BaseAbstractTest.java @@ -96,18 +96,12 @@ protected void startMParticle(MParticleOptions.Builder optionsBuilder) throws In if (identityTask == null) { identityTask = new BaseIdentityTask(); } - identityTask.addFailureListener(new TaskFailureListener() { - @Override - public void onFailure(IdentityHttpResponse result) { - fail(result.toString()); - } - }).addSuccessListener(new TaskSuccessListener() { - @Override - public void onSuccess(IdentityApiResult result) { - called.value = true; - latch.countDown(); - } - }); + identityTask + .addFailureListener(result -> fail(result.toString())) + .addSuccessListener(result -> { + called.value = true; + latch.countDown(); + }); optionsBuilder.identifyTask(identityTask); optionsBuilder = com.mparticle.AccessUtils.setCredentialsIfEmpty(optionsBuilder); diff --git a/testutils/src/main/java/com/mparticle/testutils/MPLatch.java b/testutils/src/main/java/com/mparticle/testutils/MPLatch.java index 636ac4106..93457dc17 100644 --- a/testutils/src/main/java/com/mparticle/testutils/MPLatch.java +++ b/testutils/src/main/java/com/mparticle/testutils/MPLatch.java @@ -11,7 +11,7 @@ public class MPLatch extends CountDownLatch { int countDowned = 0; int count; - Handler mHandler = new Handler(Looper.getMainLooper()); + Handler mHandler; AndroidUtils.Mutable timedOut = new AndroidUtils.Mutable<>(false); Runnable timeoutRunnable = new Runnable() { @Override @@ -30,27 +30,37 @@ public void run() { public MPLatch(int count) { super(count); this.count = count; + mHandler = new Handler(Looper.getMainLooper()); + } + + public MPLatch() { + super(1); + mHandler = new Handler(); } @Override public void countDown() { - countDowned++; - if (countDowned == count) { - mHandler.removeCallbacks(timeoutRunnable); + synchronized (this) { + countDowned++; + if (countDowned == count) { + mHandler.removeCallbacks(timeoutRunnable); + } + super.countDown(); } - super.countDown(); } @Override public void await() throws InterruptedException { int timeoutTimeMs = 5 * 1000; - if (count == countDowned) { - return; - } - mHandler.postDelayed(timeoutRunnable, timeoutTimeMs - 100L); - this.await(timeoutTimeMs, TimeUnit.MILLISECONDS); - if (timedOut.value) { - fail("timed out"); + synchronized (this) { + if (count == countDowned) { + return; + } } + mHandler.postDelayed(timeoutRunnable, timeoutTimeMs - 100L); + this.await(timeoutTimeMs, TimeUnit.MILLISECONDS); + if (timedOut.value) { + fail("timed out"); + } } }