Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
164 changes: 158 additions & 6 deletions android-core/src/main/java/com/mparticle/MParticle.java

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ public MParticleUser getUser(@NonNull Long mpid) {
*/
@NonNull
public List<MParticleUser> getUsers() {
MParticle.logRoktApiUsage("GET_USERS");
List<MParticleUser> users = new ArrayList<MParticleUser>();
Set<Long> mpids = mConfigManager.getMpids();
mpids.remove(Constants.TEMPORARY_MPID);
Expand Down Expand Up @@ -165,6 +166,7 @@ public MParticleTask<IdentityApiResult> logout() {
*/
@NonNull
public MParticleTask<IdentityApiResult> logout(@Nullable final IdentityApiRequest logoutRequest) {
MParticle.logRoktApiUsage("LOGOUT");
return makeIdentityRequest(logoutRequest, new IdentityNetworkRequestRunnable() {
@Override
public IdentityHttpResponse request(IdentityApiRequest request) throws Exception {
Expand Down Expand Up @@ -200,6 +202,7 @@ public MParticleTask<IdentityApiResult> login() {
*/
@NonNull
public MParticleTask<IdentityApiResult> login(@Nullable final IdentityApiRequest loginRequest) {
MParticle.logRoktApiUsage("LOGIN");
return makeIdentityRequest(loginRequest, new IdentityNetworkRequestRunnable() {
@Override
public IdentityHttpResponse request(IdentityApiRequest request) throws Exception {
Expand All @@ -223,6 +226,7 @@ public void onPostExecute(IdentityApiResult result) {
*/
@NonNull
public MParticleTask<IdentityApiResult> identify(@Nullable final IdentityApiRequest identifyRequest) {
MParticle.logRoktApiUsage("IDENTIFY");
return makeIdentityRequest(identifyRequest, new IdentityNetworkRequestRunnable() {
@Override
public IdentityHttpResponse request(IdentityApiRequest request) throws Exception {
Expand All @@ -246,6 +250,7 @@ public void onPostExecute(IdentityApiResult result) {
*/
@NonNull
public BaseIdentityTask modify(@NonNull final IdentityApiRequest updateRequest) {
MParticle.logRoktApiUsage("MODIFY");
boolean devMode = MPUtility.isDevEnv() || MPUtility.isAppDebuggable(mContext);
final BaseIdentityTask task = new BaseIdentityTask();

Expand Down Expand Up @@ -296,6 +301,7 @@ public void run() {
* @return
*/
public boolean aliasUsers(@NonNull AliasRequest aliasRequest) {
MParticle.logRoktApiUsage("ALIAS_USERS");
if (aliasRequest.getDestinationMpid() == 0 || aliasRequest.getSourceMpid() == 0) {
Logger.error("AliasRequest does not have a valid destinationMpid and a valid sourceMpid");
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,21 +94,25 @@ public boolean setUserAttribute(String key, Object value) {

@Override
public boolean setUserAttributeList(String key, Object value) {
MParticle.logRoktApiUsage("SET_USER_ATTRIBUTE_LIST");
Comment thread
thomson-t marked this conversation as resolved.
return mUserDelegate.setUserAttributeList(key, value, getId());
}

@Override
public boolean incrementUserAttribute(String key, Number value) {
MParticle.logRoktApiUsage("INCREMENT_USER_ATTRIBUTE");
return mUserDelegate.incrementUserAttribute(key, value, getId());
}

@Override
public boolean removeUserAttribute(String key) {
MParticle.logRoktApiUsage("REMOVE_USER_ATTRIBUTE");
return mUserDelegate.removeUserAttribute(key, getId());
}

@Override
public boolean setUserTag(@NonNull String tag) {
MParticle.logRoktApiUsage("SET_USER_TAG");
return setUserAttribute(tag, null);
}

Expand All @@ -120,11 +124,13 @@ MParticleUser setUserDelegate(MParticleUserDelegate mParticleUserDelegate) {

@Override
public ConsentState getConsentState() {
MParticle.logRoktApiUsage("GET_CONSENT_STATE");
return mUserDelegate.getConsentState(getId());
}

@Override
public void setConsentState(ConsentState state) {
MParticle.logRoktApiUsage("SET_CONSENT_STATE");
mUserDelegate.setConsentState(state, getId());
}

Expand All @@ -145,6 +151,7 @@ public long getLastSeenTime() {

@Override
public AudienceTask<AudienceResponse> getUserAudiences() {
MParticle.logRoktApiUsage("GET_USER_AUDIENCES");
return mUserDelegate.getUserAudiences(getId());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import com.mparticle.identity.IdentityApiRequest;
import com.mparticle.identity.MParticleUser;
import com.mparticle.internal.listeners.InternalListenerManager;
import com.mparticle.rokt.RoktApiDiagnosticsForwarder;
import com.mparticle.rokt.RoktOptions;

import org.json.JSONArray;
Expand Down Expand Up @@ -478,6 +479,23 @@ public Object getKitInstance(int kitId) {
return null;
}

/**
* Forwards a bounded public-API-usage diagnostic code to the Rokt kit, but only when the kit is
* active. Core stays decoupled from kit types via the {@link RoktApiDiagnosticsForwarder}
* interface. No-op when Rokt isn't integrated/active.
*/
public void logRoktApiDiagnostic(String code) {
if (code == null || code.isEmpty()) {
return;
}
if (isKitActive(MParticle.ServiceProviders.ROKT)) {
Object kit = getKitInstance(MParticle.ServiceProviders.ROKT);
if (kit instanceof RoktApiDiagnosticsForwarder) {
((RoktApiDiagnosticsForwarder) kit).onMParticleApiCall(code);
}
}
}

@Override
public Set<Integer> getSupportedKits() {
if (mKitManager != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ public void handleMessageImpl(Message msg) {

MParticle instance = MParticle.getInstance();
if (instance != null) {
instance.upload();
MParticle.withoutRoktApiUsage(instance::upload);
}
} catch (MParticleApiClientImpl.MPNoConfigException ex) {
Logger.error("Unable to Alias Request, API key and or API Secret is missing");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -826,7 +826,7 @@ public void onFailed() {
@Override
public void endUploadLoop() {
mUploadHandler.removeMessages(UploadHandler.UPLOAD_MESSAGES);
MParticle.getInstance().upload();
MParticle.withoutRoktApiUsage(MParticle.getInstance()::upload);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.mparticle.MParticle;

import java.util.concurrent.atomic.AtomicBoolean;

/**
Expand Down Expand Up @@ -37,6 +39,7 @@ public MPMediaAPI(@Nullable Context context, @NonNull MediaCallbacks callbacks)
* @param playing Is your app currently playing music for the user.
*/
public void setAudioPlaying(boolean playing) {
MParticle.logRoktApiUsage("SET_AUDIO_PLAYING");
mAudioPlaying.set(playing);
if (playing) {
mCallbacks.onAudioPlaying();
Expand All @@ -46,6 +49,7 @@ public void setAudioPlaying(boolean playing) {
}

public boolean getAudioPlaying() {
MParticle.logRoktApiUsage("GET_AUDIO_PLAYING");
return mAudioPlaying.get();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import androidx.localbroadcastmanager.content.LocalBroadcastManager;

import com.mparticle.MPService;
import com.mparticle.MParticle;
import com.mparticle.internal.ConfigManager;
import com.mparticle.internal.Logger;
import com.mparticle.internal.MPUtility;
Expand Down Expand Up @@ -76,10 +77,12 @@ public void enablePushNotifications(@NonNull String senderId) {
* Unregister the application for FCM notifications.
*/
public void disablePushNotifications() {
MParticle.logRoktApiUsage("DISABLE_PUSH_NOTIFICATIONS");
ConfigManager.getInstance(mContext).clearPushRegistration();
}

public void displayPushNotificationByDefault(@Nullable Boolean enabled) {
MParticle.logRoktApiUsage("DISPLAY_PUSH_NOTIFICATION_BY_DEFAULT");
ConfigManager.getInstance(mContext).setDisplayPushNotifications(enabled);
}

Expand All @@ -91,6 +94,7 @@ public void displayPushNotificationByDefault(@Nullable Boolean enabled) {
* @see PushAnalyticsReceiver
*/
public void registerPushAnalyticsReceiver(@NonNull PushAnalyticsReceiver receiver) {
MParticle.logRoktApiUsage("REGISTER_PUSH_ANALYTICS_RECEIVER");
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(BROADCAST_NOTIFICATION_RECEIVED);
intentFilter.addAction(BROADCAST_NOTIFICATION_TAPPED);
Expand All @@ -105,6 +109,7 @@ public void registerPushAnalyticsReceiver(@NonNull PushAnalyticsReceiver receive
* @see PushAnalyticsReceiver
*/
public void unregisterPushAnalyticsReceiver(@Nullable PushAnalyticsReceiver receiver) {
MParticle.logRoktApiUsage("UNREGISTER_PUSH_ANALYTICS_RECEIVER");
LocalBroadcastManager.getInstance(mContext).unregisterReceiver(receiver);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.mparticle.rokt

/**
* Implemented by the Rokt kit so mParticle core can forward a bounded, non-PII public-API-usage
* diagnostic code into the Rokt SDK — without core depending on any kit types. Core resolves the
* live Rokt kit via `getKitInstance` and calls this only when the kit is active.
*/
interface RoktApiDiagnosticsForwarder {
fun onMParticleApiCall(code: String)
}
105 changes: 105 additions & 0 deletions android-core/src/test/kotlin/com/mparticle/MParticleTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ package com.mparticle
import android.os.Looper
import android.os.SystemClock
import android.webkit.WebView
import com.mparticle.commerce.CommerceEvent
import com.mparticle.commerce.Impression
import com.mparticle.commerce.Product
import com.mparticle.commerce.Promotion
import com.mparticle.commerce.TransactionAttributes
import com.mparticle.identity.IdentityApi
import com.mparticle.identity.IdentityApiRequest
import com.mparticle.identity.MParticleUser
Expand Down Expand Up @@ -504,6 +509,106 @@ class MParticleTest {
verify(instance.mKitManager).setWrapperSdkVersion(WrapperSdkVersion(expectedSdk, expectedVersion))
}

@Test
fun logRoktApiUsage_forwardsToKitManager_butIsSuppressedForInternalCalls() {
val instance: MParticle = InnerMockMParticle()
MParticle.setInstance(instance)

// A genuine partner call forwards the code to the (active-kit-gated) kit manager.
MParticle.logRoktApiUsage("SELECT_PLACEMENTS")
verify(instance.mKitManager, Mockito.times(1)).logRoktApiDiagnostic("SELECT_PLACEMENTS")

// An SDK/kit-internal call routed through withoutRoktApiUsage must NOT be reported.
MParticle.withoutRoktApiUsage {
MParticle.logRoktApiUsage("LOG_EVENT")
}
verify(instance.mKitManager, Mockito.times(0)).logRoktApiDiagnostic("LOG_EVENT")

// Suppression is scoped: a later partner call is reported again.
MParticle.logRoktApiUsage("CLOSE")
verify(instance.mKitManager, Mockito.times(1)).logRoktApiDiagnostic("CLOSE")
}

@Test
fun logEvent_reportsBoundedEventTypes() {
val instance: MParticle = InnerMockMParticle()
MParticle.setInstance(instance)
val expectedCodes = mutableListOf<String>()

linkedMapOf(
MParticle.EventType.Unknown to "LOG_EVENT_UNKNOWN",
MParticle.EventType.Navigation to "LOG_EVENT_NAVIGATION",
MParticle.EventType.Location to "LOG_EVENT_LOCATION",
MParticle.EventType.Search to "LOG_EVENT_SEARCH",
MParticle.EventType.Transaction to "LOG_EVENT_TRANSACTION",
MParticle.EventType.UserContent to "LOG_EVENT_USER_CONTENT",
MParticle.EventType.UserPreference to "LOG_EVENT_USER_PREFERENCE",
MParticle.EventType.Social to "LOG_EVENT_SOCIAL",
MParticle.EventType.Other to "LOG_EVENT_OTHER",
MParticle.EventType.Media to "LOG_EVENT_MEDIA",
).forEach { (eventType, code) ->
instance.logEvent(MPEvent.Builder("event", eventType).shouldUploadEvent(false).build())
expectedCodes.add(code)
}

val product = Product.Builder("name", "sku", 1.0).build()
linkedMapOf(
Product.ADD_TO_CART to "LOG_EVENT_PRODUCT_ADD_TO_CART",
Product.REMOVE_FROM_CART to "LOG_EVENT_PRODUCT_REMOVE_FROM_CART",
Product.ADD_TO_WISHLIST to "LOG_EVENT_PRODUCT_ADD_TO_WISHLIST",
Product.REMOVE_FROM_WISHLIST to "LOG_EVENT_PRODUCT_REMOVE_FROM_WISHLIST",
Product.CHECKOUT to "LOG_EVENT_PRODUCT_CHECKOUT",
Product.CHECKOUT_OPTION to "LOG_EVENT_PRODUCT_CHECKOUT_OPTION",
Product.CLICK to "LOG_EVENT_PRODUCT_CLICK",
Product.DETAIL to "LOG_EVENT_PRODUCT_VIEW_DETAIL",
Product.PURCHASE to "LOG_EVENT_PRODUCT_PURCHASE",
Product.REFUND to "LOG_EVENT_PRODUCT_REFUND",
).forEach { (action, code) ->
val builder = CommerceEvent.Builder(action, product).shouldUploadEvent(false)
if (action == Product.PURCHASE || action == Product.REFUND) {
builder.transactionAttributes(TransactionAttributes().setId(action))
}
instance.logEvent(builder.build())
expectedCodes.add(code)
}

linkedMapOf(
Promotion.VIEW to "LOG_EVENT_PROMOTION_VIEW",
Promotion.CLICK to "LOG_EVENT_PROMOTION_CLICK",
).forEach { (action, code) ->
instance.logEvent(CommerceEvent.Builder(action, Promotion()).shouldUploadEvent(false).build())
expectedCodes.add(code)
}

instance.logEvent(CommerceEvent.Builder(Impression("list", product)).shouldUploadEvent(false).build())
expectedCodes.add("LOG_EVENT_PRODUCT_IMPRESSION")
instance.logEvent(CommerceEvent.Builder("partner-supplied", product).shouldUploadEvent(false).build())
expectedCodes.add("LOG_EVENT_COMMERCE_OTHER")
instance.logEvent(Mockito.mock(BaseEvent::class.java))
expectedCodes.add("LOG_EVENT_OTHER")

val codeCaptor = ArgumentCaptor.forClass(String::class.java)
verify(instance.mKitManager, Mockito.times(expectedCodes.size)).logRoktApiDiagnostic(codeCaptor.capture())
Assert.assertEquals(expectedCodes, codeCaptor.allValues)
}

@Test
fun setUpdateInterval_doesNotReportItsInternalUpload() {
val instance: MParticle = InnerMockMParticle()
MParticle.setInstance(instance)

instance.setUpdateInterval(60)

verify(instance.mKitManager).logRoktApiDiagnostic("SET_UPLOAD_INTERVAL")
verify(instance.mKitManager, Mockito.never()).logRoktApiDiagnostic("UPLOAD")
verify(instance.mMessageManager).doUpload()

instance.upload()

verify(instance.mKitManager).logRoktApiDiagnostic("UPLOAD")
verify(instance.mMessageManager, Mockito.times(2)).doUpload()
}

inner class InnerMockMParticle : MParticle() {
init {
mConfigManager = ConfigManager(MockContext())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class ApiVisibilityTest {
publicMethodCount++
}
}
Assert.assertEquals(65, publicMethodCount)
Assert.assertEquals(67, publicMethodCount)
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import com.mparticle.WrapperSdk
import com.mparticle.WrapperSdkVersion
import com.mparticle.commerce.CommerceEvent
import com.mparticle.internal.PushRegistrationHelper.PushRegistration
import com.mparticle.rokt.RoktApiDiagnosticsForwarder
import com.mparticle.testutils.RandomUtils
import org.json.JSONArray
import org.junit.Assert
Expand All @@ -32,6 +33,43 @@ import kotlin.test.assertEquals

@RunWith(PowerMockRunner::class)
class KitFrameworkWrapperTest {
private fun newWrapper(): KitFrameworkWrapper = KitFrameworkWrapper(
Mockito.mock(Context::class.java),
Mockito.mock(ReportingManager::class.java),
Mockito.mock(ConfigManager::class.java),
Mockito.mock(AppStateManager::class.java),
true,
Mockito.mock(MParticleOptions::class.java),
)

@Test
fun logRoktApiDiagnostic_forwardsToActiveRoktKit() {
val wrapper = newWrapper()
val mockKitManager = Mockito.mock(KitManager::class.java)
wrapper.setKitManager(mockKitManager)
val forwarder = Mockito.mock(RoktApiDiagnosticsForwarder::class.java)
`when`(mockKitManager.isKitActive(MParticle.ServiceProviders.ROKT)).thenReturn(true)
`when`(mockKitManager.getKitInstance(MParticle.ServiceProviders.ROKT)).thenReturn(forwarder)

wrapper.logRoktApiDiagnostic("LOG_EVENT")

verify(forwarder, times(1)).onMParticleApiCall("LOG_EVENT")
}

@Test
fun logRoktApiDiagnostic_noOpWhenRoktKitInactive() {
val wrapper = newWrapper()
val mockKitManager = Mockito.mock(KitManager::class.java)
wrapper.setKitManager(mockKitManager)
val forwarder = Mockito.mock(RoktApiDiagnosticsForwarder::class.java)
`when`(mockKitManager.isKitActive(MParticle.ServiceProviders.ROKT)).thenReturn(false)
`when`(mockKitManager.getKitInstance(MParticle.ServiceProviders.ROKT)).thenReturn(forwarder)

wrapper.logRoktApiDiagnostic("LOG_EVENT")

verify(forwarder, times(0)).onMParticleApiCall(Mockito.anyString())
}

@Test
@Throws(Exception::class)
fun testLoadKitLibrary() {
Expand Down
Loading
Loading