diff --git a/android-core/src/main/java/com/mparticle/SdkListener.java b/android-core/src/main/java/com/mparticle/SdkListener.java
index f11103781..2736307c6 100644
--- a/android-core/src/main/java/com/mparticle/SdkListener.java
+++ b/android-core/src/main/java/com/mparticle/SdkListener.java
@@ -15,7 +15,7 @@
/**
* Note: This is an Experimental feature. Adding an instance of SdkListener will slow down the SDK and
* should be used only in development.
- *
+ *
* SdkListener is a new feature which enables updates on and visibility into internal Events occuring
* inside the SDK.
*/
@@ -123,7 +123,7 @@ public void onKitStarted(int kitId) {
* @param kitId the id of the kit, corresponse with a {@link com.mparticle.MParticle.ServiceProviders}
* @param reason a message containing the reason a kit was stopped
*/
- public void onKitExcluded(int kitId, @NonNull String reason) {
+ public void onKitExcluded(int kitId, @Nullable String reason) {
}
diff --git a/android-core/src/main/java/com/mparticle/internal/listeners/ApiClass.java b/android-core/src/main/java/com/mparticle/internal/listeners/ApiClass.java
deleted file mode 100644
index 0812efcf3..000000000
--- a/android-core/src/main/java/com/mparticle/internal/listeners/ApiClass.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.mparticle.internal.listeners;
-
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-@Retention(RetentionPolicy.CLASS)
-@Target(ElementType.TYPE)
-public @interface ApiClass {
-}
diff --git a/android-core/src/main/java/com/mparticle/internal/listeners/GraphListener.java b/android-core/src/main/java/com/mparticle/internal/listeners/GraphListener.java
deleted file mode 100644
index 973a9fe94..000000000
--- a/android-core/src/main/java/com/mparticle/internal/listeners/GraphListener.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.mparticle.internal.listeners;
-
-import android.os.Message;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-interface GraphListener {
- void onCompositeObjects(@Nullable Object child, @Nullable Object parent);
-
- void onThreadMessage(@NonNull String handlerName, @Nullable Message msg, boolean onNewThread, @Nullable StackTraceElement[] stackTrace);
-}
diff --git a/android-core/src/main/java/com/mparticle/internal/listeners/InternalListenerManager.java b/android-core/src/main/java/com/mparticle/internal/listeners/InternalListenerManager.java
deleted file mode 100644
index 4b8d456db..000000000
--- a/android-core/src/main/java/com/mparticle/internal/listeners/InternalListenerManager.java
+++ /dev/null
@@ -1,376 +0,0 @@
-package com.mparticle.internal.listeners;
-
-import android.content.ContentValues;
-import android.content.Context;
-import android.os.Message;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.mparticle.SdkListener;
-import com.mparticle.identity.AliasResponse;
-import com.mparticle.internal.InternalSession;
-import com.mparticle.internal.KitFrameworkWrapper;
-import com.mparticle.internal.MPUtility;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.lang.ref.WeakReference;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-public class InternalListenerManager implements InternalListener {
- private static InternalListenerManager instance = null;
- private static final String INTERNAL_LISTENER_PROP = "debug.mparticle.listener";
- private Context context;
- final List> sdkListeners = new ArrayList>();
- final List> graphListeners = new ArrayList>();
- private boolean thrown = false;
-
- private InternalListenerManager(Context context) {
- this.context = context;
- }
-
- @Nullable
- public static InternalListenerManager start(Context context) {
- boolean canRun = MPUtility.isAppDebuggable(context) || context.getPackageName().equals(MPUtility.getProp(INTERNAL_LISTENER_PROP));
- if (instance == null && context != null && canRun) {
- instance = new InternalListenerManager(context.getApplicationContext());
- }
- return instance;
- }
-
- public static boolean isEnabled() {
- return instance != null &&
- instance.hasListeners();
- }
-
- @NonNull
- public static InternalListener getListener() {
- if (instance != null && isEnabled()) {
- return instance;
- } else {
- return InternalListener.EMPTY;
- }
- }
-
- public void addListener(SdkListener sdkListener) {
- for (WeakReference listener : sdkListeners) {
- if (listener.get() == sdkListener) {
- return;
- }
- }
- sdkListeners.add(new WeakReference(sdkListener));
- if (sdkListener instanceof GraphListener) {
- graphListeners.add(new WeakReference((GraphListener) sdkListener));
- }
- }
-
- public void removeListener(SdkListener sdkListener) {
- for (WeakReference listener : new ArrayList>(sdkListeners)) {
- if (listener.get() == sdkListener) {
- sdkListeners.remove(listener);
- }
- }
- for (WeakReference listener : new ArrayList>(graphListeners)) {
- if (listener.get() == sdkListener) {
- graphListeners.remove(listener);
- }
- }
- }
-
- @Override
- public void onApiCalled(final Object... objects) {
-
- }
-
- @Override
- public void onKitApiCalled(int kitId, Boolean used, Object... objects) {
- StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
- String methodName = null;
- for (int i = 0; i < stackTrace.length; i++) {
- StackTraceElement element = stackTrace[i];
- if (element.getClassName().equals(KitFrameworkWrapper.class.getName())) {
- methodName = element.getMethodName() + "()";
- }
- }
- onKitApiCalled(stackTrace, methodName, kitId, used, objects);
- }
-
- @Override
- public void onKitApiCalled(String methodName, int kitId, Boolean used, Object... objects) {
- onKitApiCalled(Thread.currentThread().getStackTrace(), methodName, kitId, used, objects);
- }
-
- private void onKitApiCalled(StackTraceElement[] stackTrace, final String methodName, final int kitId, final boolean used, Object... objects) {
- String invokingApiMethodName = null;
- String kitManagerMethodName = null;
- boolean foundInternal = false;
- boolean foundExternal = false;
- final List objectList = new ArrayList();
- for (Object obj : objects) {
- objectList.add(obj);
- }
- for (int i = 0; i < stackTrace.length; i++) {
- if (!isExternalApiInvocation(stackTrace[i])) {
- foundInternal = true;
- }
- if (foundInternal && !foundExternal) {
- if (isExternalApiInvocation(stackTrace[i])) {
- invokingApiMethodName = getApiName(stackTrace[i - 1]);
- foundExternal = true;
- }
- }
- if (stackTrace[i].getClassName().equals("com.mparticle.kits.KitManagerImpl")) {
- kitManagerMethodName = getApiName(stackTrace[i]);
- }
- }
- final String finalInvokingApiMethodName = invokingApiMethodName;
- final String finalKitManagerMethodName = kitManagerMethodName;
- broadcast(new SdkListenerRunnable() {
- @Override
- public void run(SdkListener listener) {
- listener.onKitApiCalled(kitId, methodName, finalInvokingApiMethodName, finalKitManagerMethodName, objectList, used);
- }
- });
- }
-
-
- @Override
- public void onCompositeObjects(final Object child, final Object parent) {
- broadcast(new SdkGraphListenerRunnable() {
- @Override
- public void run(GraphListener listener) {
- listener.onCompositeObjects(child, parent);
- }
- });
- }
-
- @Override
- public void onThreadMessage(final String handlerName, final Message msg, final boolean onNewThread) {
- StackTraceElement[] stackTrace = null;
- if (!onNewThread) {
- stackTrace = Thread.currentThread().getStackTrace();
- }
- final StackTraceElement[] finalStackTrace = stackTrace;
- broadcast(new SdkGraphListenerRunnable() {
- @Override
- public void run(GraphListener listener) {
- listener.onThreadMessage(handlerName, msg, onNewThread, finalStackTrace);
- }
- });
- }
-
- @Override
- public void onEntityStored(final Long primaryKey, final String tableName, ContentValues contentValues) {
- onCompositeObjects(contentValues, tableName + primaryKey);
- final JSONObject jsonObject = new JSONObject();
- SdkListener.DatabaseTable table = null;
- try {
- table = SdkListener.DatabaseTable.valueOf(tableName.toUpperCase());
- } catch (IllegalArgumentException ex) {
- table = SdkListener.DatabaseTable.UNKNOWN;
- }
- for (Map.Entry entry : contentValues.valueSet()) {
- try {
- if (entry.getValue() == null) {
- jsonObject.put(entry.getKey(), JSONObject.NULL);
- } else {
- jsonObject.put(entry.getKey(), entry.getValue());
- }
- } catch (JSONException e) {
- e.printStackTrace();
- }
- }
- final SdkListener.DatabaseTable finalTable = table;
- broadcast(new SdkListenerRunnable() {
- @Override
- public void run(SdkListener listener) {
- listener.onEntityStored(finalTable, primaryKey, jsonObject);
- }
- });
- }
-
- @Override
- public void onNetworkRequestStarted(final SdkListener.Endpoint type, final String url, final JSONObject body, Object... objects) {
- for (Object obj : objects) {
- onCompositeObjects(obj, body);
- }
- final List objectList = new ArrayList();
- for (Object obj : objects) {
- objectList.add(obj);
- }
- broadcast(new SdkListenerRunnable() {
- @Override
- public void run(SdkListener listener) {
- listener.onNetworkRequestStarted(type, url, body);
- }
- });
- }
-
- @Override
- public void onNetworkRequestFinished(final SdkListener.Endpoint type, final String url, final JSONObject response, final int responseCode) {
- broadcast(new SdkListenerRunnable() {
- @Override
- public void run(SdkListener listener) {
- listener.onNetworkRequestFinished(type, url, response, responseCode);
- }
- });
- }
-
- @Override
- public void onSessionUpdated(final InternalSession internalSession) {
- broadcast(new SdkListenerRunnable() {
- @Override
- public void run(SdkListener listener) {
- listener.onSessionUpdated(new InternalSession(internalSession));
- }
- });
- }
-
- @Override
- public void onKitDetected(final int kitId) {
- broadcast(new SdkListenerRunnable() {
- @Override
- public void run(SdkListener listener) {
- listener.onKitDetected(kitId);
- }
- });
- }
-
- @Override
- public void onKitConfigReceived(final int kitId, final String configuration) {
- JSONObject jsonObject = new JSONObject();
- try {
- jsonObject = new JSONObject(configuration);
- } catch (JSONException e) {
- }
- final JSONObject jsonConfig = jsonObject;
- broadcast(new SdkListenerRunnable() {
- @Override
- public void run(SdkListener listener) {
- listener.onKitConfigReceived(kitId, jsonConfig);
- }
- });
- }
-
- @Override
- public void onKitExcluded(final int kitId, final String reason) {
- broadcast(new SdkListenerRunnable() {
- @Override
- public void run(SdkListener listener) {
- listener.onKitExcluded(kitId, reason);
- }
- });
- }
-
- @Override
- public void onKitStarted(final int kitId) {
- broadcast(new SdkListenerRunnable() {
- @Override
- public void run(SdkListener listener) {
- listener.onKitStarted(kitId);
- }
- });
- }
-
- @Override
- public void onAliasRequestFinished(final AliasResponse aliasResponse) {
- broadcast(new SdkListenerRunnable() {
- @Override
- public void run(SdkListener listener) {
- listener.onAliasRequestFinished(aliasResponse);
- }
- });
- }
-
- private void broadcast(SdkListenerRunnable runnable) {
- for (WeakReference listenerRef : new ArrayList>(sdkListeners)) {
- SdkListener listener = listenerRef.get();
- if (listener == null) {
- sdkListeners.remove(listenerRef);
- } else {
- runnable.run(listener);
- }
- }
- }
-
- private void broadcast(SdkGraphListenerRunnable runnable) {
- for (WeakReference listenerRef : new ArrayList>(graphListeners)) {
- GraphListener listener = listenerRef.get();
- if (listener == null) {
- graphListeners.remove(listenerRef);
- } else {
- runnable.run(listener);
- }
- }
- }
-
- interface SdkListenerRunnable {
- void run(SdkListener listener);
- }
-
- interface SdkGraphListenerRunnable {
- void run(GraphListener listener);
- }
-
-
- private String getApiName(StackTraceElement element) {
- String classNameString = getClassName(element.getClassName(), element.getMethodName());
- return getApiFormattedName(classNameString, element.getMethodName());
- }
-
- public static String getApiFormattedName(String className, String methodName) {
- return new StringBuilder()
- .append(className)
- .append(".")
- .append(methodName)
- .append("()")
- .toString();
- }
-
- private String getClassName(String className, String methodName) {
- String[] packageNames = className.split("\\.");
- String simpleClassName = packageNames[packageNames.length - 1];
- if (isObfuscated(simpleClassName)) {
- try {
- List superClasses = new ArrayList();
- Class clazz = Class.forName(className); // nosemgrep
- superClasses.add(clazz.getSuperclass());
- for (Class interfce : clazz.getInterfaces()) {
- superClasses.add(interfce);
- }
- for (Class superClass : superClasses) {
- for (Method method : superClass.getMethods()) {
- if (method.getName().equals(methodName)) {
- String superClassName = getClassName(superClass.getName(), methodName);
- if (!isObfuscated(superClassName)) {
- return superClassName;
- }
- }
- }
- }
- } catch (ClassNotFoundException e) {
- e.printStackTrace();
- }
- }
- return simpleClassName;
- }
-
- private boolean isObfuscated(@NonNull String className) {
- return Character.isLowerCase(className.toCharArray()[0]) && className.length() <= 3;
- }
-
- private boolean isExternalApiInvocation(StackTraceElement element) {
- return !element.getClassName().startsWith("com.mparticle") ||
- (element.getClassName().startsWith(context.getApplicationContext().getPackageName()) &&
- context.getApplicationContext().getPackageName().length() > 1);
- }
-
- private boolean hasListeners() {
- return instance.sdkListeners.size() > 0 || instance.graphListeners.size() > 0;
- }
-}
diff --git a/android-core/src/main/kotlin/com/mparticle/internal/AppStateManager.kt b/android-core/src/main/kotlin/com/mparticle/internal/AppStateManager.kt
index 429f0c25f..1d01f997b 100644
--- a/android-core/src/main/kotlin/com/mparticle/internal/AppStateManager.kt
+++ b/android-core/src/main/kotlin/com/mparticle/internal/AppStateManager.kt
@@ -392,7 +392,7 @@ open class AppStateManager @JvmOverloads constructor(
session = InternalSession()
val instance = MParticle.getInstance()
instance?.Internal()?.kitManager?.onSessionEnd()
- InternalListenerManager.getListener().onSessionUpdated(session)
+ InternalListenerManager.listener.onSessionUpdated(session)
}
private fun disableLocationTracking() {
diff --git a/android-core/src/main/kotlin/com/mparticle/internal/listeners/ApiClass.kt b/android-core/src/main/kotlin/com/mparticle/internal/listeners/ApiClass.kt
new file mode 100644
index 000000000..da86188a2
--- /dev/null
+++ b/android-core/src/main/kotlin/com/mparticle/internal/listeners/ApiClass.kt
@@ -0,0 +1,5 @@
+package com.mparticle.internal.listeners
+
+@Retention(AnnotationRetention.BINARY)
+@Target(AnnotationTarget.CLASS)
+annotation class ApiClass
diff --git a/android-core/src/main/kotlin/com/mparticle/internal/listeners/GraphListener.kt b/android-core/src/main/kotlin/com/mparticle/internal/listeners/GraphListener.kt
new file mode 100644
index 000000000..495a722ea
--- /dev/null
+++ b/android-core/src/main/kotlin/com/mparticle/internal/listeners/GraphListener.kt
@@ -0,0 +1,14 @@
+package com.mparticle.internal.listeners
+
+import android.os.Message
+
+interface GraphListener {
+ fun onCompositeObjects(child: Any?, parent: Any?)
+
+ fun onThreadMessage(
+ handlerName: String,
+ msg: Message?,
+ onNewThread: Boolean,
+ stackTrace: Array?
+ )
+}
diff --git a/android-core/src/main/java/com/mparticle/internal/listeners/InternalListener.java b/android-core/src/main/kotlin/com/mparticle/internal/listeners/InternalListener.kt
similarity index 52%
rename from android-core/src/main/java/com/mparticle/internal/listeners/InternalListener.java
rename to android-core/src/main/kotlin/com/mparticle/internal/listeners/InternalListener.kt
index e7d546a84..0f496e212 100644
--- a/android-core/src/main/java/com/mparticle/internal/listeners/InternalListener.java
+++ b/android-core/src/main/kotlin/com/mparticle/internal/listeners/InternalListener.kt
@@ -1,21 +1,15 @@
-package com.mparticle.internal.listeners;
-
-import android.content.ContentValues;
-import android.os.Build;
-import android.os.Message;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-
-import com.mparticle.SdkListener;
-import com.mparticle.identity.AliasResponse;
-import com.mparticle.internal.InternalSession;
-
-import org.json.JSONObject;
-
-public interface InternalListener {
-
+package com.mparticle.internal.listeners
+
+import android.content.ContentValues
+import android.os.Build
+import android.os.Message
+import androidx.annotation.RequiresApi
+import com.mparticle.SdkListener
+import com.mparticle.identity.AliasResponse
+import com.mparticle.internal.InternalSession
+import org.json.JSONObject
+
+interface InternalListener {
/**
* This method should be called within the body of a public API method. Generally
* we only want to instrument API methods which "do something", i.e., log an event or change
@@ -23,7 +17,7 @@ public interface InternalListener {
*
* @param objects the arguments passed into the API method
*/
- void onApiCalled(Object... objects);
+ fun onApiCalled(vararg objects: Any)
/**
* To be called when a Kit's API method is invoked. This overloaded variant should be used when
@@ -34,7 +28,7 @@ public interface InternalListener {
* @param used whether the Kit's method returned ReportingMessages, or null if return type is void
* @param objects the arguments supplied to the Kit
*/
- void onKitApiCalled(int kitId, Boolean used, Object... objects);
+ fun onKitApiCalled(kitId: Int, used: Boolean, vararg objects: Any)
/**
* to be called when a Kit's API method is invoked, and the name of the Kit's method is different
@@ -45,7 +39,7 @@ public interface InternalListener {
* @param used whether the Kit's method returned ReportingMessages, or null if return type is void
* @param objects the arguments supplied to the Kit
*/
- void onKitApiCalled(String methodName, int kitId, Boolean used, Object... objects);
+ fun onKitApiCalled(methodName: String, kitId: Int, used: Boolean, vararg objects: Any)
/**
* establishes a child-parent relationship between two objects. It is not necessary to call this
@@ -54,7 +48,7 @@ public interface InternalListener {
* @param child the child object
* @param parent the parent object
*/
- void onCompositeObjects(@Nullable Object child, @Nullable Object parent);
+ fun onCompositeObjects(child: Any?, parent: Any?)
/**
* denotes that an object is going to be passed to a new Thread, and is a candidate to be a "composite"
@@ -64,7 +58,7 @@ public interface InternalListener {
* @param handlerName the Name of the Handler class, for example "com.mparticle.internal.MessageHandler"
* @param msg the Message object
*/
- void onThreadMessage(@NonNull String handlerName, @NonNull Message msg, boolean onNewThread);
+ fun onThreadMessage(handlerName: String, msg: Message, onNewThread: Boolean)
/**
* indicates that an entry has been stored in the Database
@@ -74,7 +68,7 @@ public interface InternalListener {
* @param contentValues the ContentValues object to be inserted
*/
@RequiresApi(api = Build.VERSION_CODES.HONEYCOMB)
- void onEntityStored(Long rowId, String tableName, ContentValues contentValues);
+ fun onEntityStored(rowId: Long, tableName: String, contentValues: ContentValues?)
/**
* indicates that a Network Request has been started
@@ -84,7 +78,12 @@ public interface InternalListener {
* @param body the request body
* @param objects any underlying objects that the request body is derived from, for example, an IdentityApiRequest instance
*/
- void onNetworkRequestStarted(SdkListener.Endpoint type, String url, JSONObject body, Object... objects);
+ fun onNetworkRequestStarted(
+ type: SdkListener.Endpoint,
+ url: String,
+ body: JSONObject?,
+ vararg objects: Any
+ )
/**
* indicates that a NetworkRequest has been finished
@@ -93,7 +92,12 @@ public interface InternalListener {
* @param response the response body
* @param responseCode the response code
*/
- void onNetworkRequestFinished(SdkListener.Endpoint type, String url, JSONObject response, int responseCode);
+ fun onNetworkRequestFinished(
+ type: SdkListener.Endpoint,
+ url: String,
+ response: JSONObject?,
+ responseCode: Int
+ )
/**
* this should be called when the current Session changes, for example, it starts, stops or the
@@ -101,14 +105,14 @@ public interface InternalListener {
*
* @param internalSession
*/
- void onSessionUpdated(InternalSession internalSession);
+ fun onSessionUpdated(internalSession: InternalSession)
/**
* indicates that a Kit dependency is present
*
* @param kitId
*/
- void onKitDetected(int kitId);
+ fun onKitDetected(kitId: Int)
/**
* indicates that we have received a configuration for a Kit
@@ -116,7 +120,7 @@ public interface InternalListener {
* @param kitId
* @param configuration
*/
- void onKitConfigReceived(int kitId, String configuration);
+ fun onKitConfigReceived(kitId: Int, configuration: String?)
/**
* indicates that a Kit was present, and a configuration was received for it, but it was not started,
@@ -126,44 +130,88 @@ public interface InternalListener {
* @param kitId
* @param reason
*/
- void onKitExcluded(int kitId, String reason);
+ fun onKitExcluded(kitId: Int, reason: String?)
/**
* indicates that a Kit successfully executed it's onKitCreate() method
*
* @param kitId
*/
- void onKitStarted(int kitId);
-
- void onAliasRequestFinished(AliasResponse aliasResponse);
-
- InternalListener EMPTY = new InternalListener() {
- public void onApiCalled(Object... objects) { /* stub */}
-
- public void onKitApiCalled(int kitId, Boolean used, Object... objects) { /* stub */}
-
- public void onKitApiCalled(String methodName, int kitId, Boolean used, Object... objects) { /* stub */}
-
- public void onEntityStored(Long rowId, String tableName, ContentValues contentValues) { /* stub */}
-
- public void onNetworkRequestStarted(SdkListener.Endpoint type, String url, JSONObject body, Object... objects) { /* stub */}
-
- public void onNetworkRequestFinished(SdkListener.Endpoint type, String url, JSONObject response, int responseCode) { /* stub */}
-
- public void onSessionUpdated(InternalSession internalSession) { /* stub */}
-
- public void onKitDetected(int kitId) { /* stub */}
-
- public void onKitConfigReceived(int kitId, String configuration) { /* stub */}
-
- public void onKitExcluded(int kitId, String reason) { /* stub */}
-
- public void onKitStarted(int kitId) { /* stub */}
-
- public void onAliasRequestFinished(AliasResponse aliasResponse) { /* stub */}
-
- public void onCompositeObjects(Object child, Object parent) { /* stub */}
-
- public void onThreadMessage(String handlerName, Message msg, boolean onNewThread) { /* stub */ }
- };
+ fun onKitStarted(kitId: Int)
+
+ fun onAliasRequestFinished(aliasResponse: AliasResponse?)
+
+ companion object {
+ @JvmField
+ val EMPTY: InternalListener = object : InternalListener {
+ override fun onApiCalled(vararg objects: Any) { /* stub */
+ }
+
+ override fun onKitApiCalled(
+ kitId: Int,
+ used: Boolean,
+ vararg objects: Any
+ ) { /* stub */
+ }
+
+ override fun onKitApiCalled(
+ methodName: String,
+ kitId: Int,
+ used: Boolean,
+ vararg objects: Any
+ ) { /* stub */
+ }
+
+ override fun onEntityStored(
+ rowId: Long,
+ tableName: String,
+ contentValues: ContentValues?
+ ) { /* stub */
+ }
+
+ override fun onNetworkRequestStarted(
+ type: SdkListener.Endpoint,
+ url: String,
+ body: JSONObject?,
+ vararg objects: Any
+ ) { /* stub */
+ }
+
+ override fun onNetworkRequestFinished(
+ type: SdkListener.Endpoint,
+ url: String,
+ response: JSONObject?,
+ responseCode: Int
+ ) { /* stub */
+ }
+
+ override fun onSessionUpdated(internalSession: InternalSession) { /* stub */
+ }
+
+ override fun onKitDetected(kitId: Int) { /* stub */
+ }
+
+ override fun onKitConfigReceived(kitId: Int, configuration: String?) { /* stub */
+ }
+
+ override fun onKitExcluded(kitId: Int, reason: String?) { /* stub */
+ }
+
+ override fun onKitStarted(kitId: Int) { /* stub */
+ }
+
+ override fun onAliasRequestFinished(aliasResponse: AliasResponse?) { /* stub */
+ }
+
+ override fun onCompositeObjects(child: Any?, parent: Any?) { /* stub */
+ }
+
+ override fun onThreadMessage(
+ handlerName: String,
+ msg: Message,
+ onNewThread: Boolean
+ ) { /* stub */
+ }
+ }
+ }
}
diff --git a/android-core/src/main/kotlin/com/mparticle/internal/listeners/InternalListenerManager.kt b/android-core/src/main/kotlin/com/mparticle/internal/listeners/InternalListenerManager.kt
new file mode 100644
index 000000000..27f95ada2
--- /dev/null
+++ b/android-core/src/main/kotlin/com/mparticle/internal/listeners/InternalListenerManager.kt
@@ -0,0 +1,353 @@
+package com.mparticle.internal.listeners
+
+import android.content.ContentValues
+import android.content.Context
+import android.os.Message
+import com.mparticle.SdkListener
+import com.mparticle.identity.AliasResponse
+import com.mparticle.internal.InternalSession
+import com.mparticle.internal.KitFrameworkWrapper
+import com.mparticle.internal.MPUtility
+import org.json.JSONException
+import org.json.JSONObject
+import java.lang.ref.WeakReference
+import java.util.Locale
+
+class InternalListenerManager private constructor(private val context: Context) : InternalListener {
+ val sdkListeners: MutableList> = ArrayList()
+ val graphListeners: MutableList> = ArrayList()
+ private val thrown = false
+
+ fun addListener(sdkListener: SdkListener) {
+ for (listener in sdkListeners) {
+ if (listener.get() === sdkListener) {
+ return
+ }
+ }
+ sdkListeners.add(WeakReference(sdkListener))
+ if (sdkListener is GraphListener) {
+ graphListeners.add(WeakReference(sdkListener as GraphListener))
+ }
+ }
+
+ fun removeListener(sdkListener: SdkListener) {
+ for (listener in ArrayList(sdkListeners)) {
+ if (listener.get() === sdkListener) {
+ sdkListeners.remove(listener)
+ }
+ }
+ for (listener in ArrayList(graphListeners)) {
+ if (listener.get() === sdkListener) {
+ graphListeners.remove(listener)
+ }
+ }
+ }
+
+ override fun onApiCalled(vararg objects: Any) {
+ }
+
+ override fun onKitApiCalled(kitId: Int, used: Boolean, vararg objects: Any) {
+ val stackTrace = Thread.currentThread().stackTrace
+ var methodName: String? = null
+ for (i in stackTrace.indices) {
+ val element = stackTrace[i]
+ if (element.className == KitFrameworkWrapper::class.java.name) {
+ methodName = element.methodName + "()"
+ }
+ }
+ methodName?.let { onKitApiCalled(stackTrace, it, kitId, used, *objects) }
+ }
+
+ override fun onKitApiCalled(
+ methodName: String, kitId: Int, used: Boolean, vararg objects: Any
+ ) {
+ onKitApiCalled(Thread.currentThread().stackTrace, methodName, kitId, used, *objects)
+ }
+
+ private fun onKitApiCalled(
+ stackTrace: Array,
+ methodName: String,
+ kitId: Int,
+ used: Boolean,
+ vararg objects: Any
+ ) {
+ var invokingApiMethodName: String? = null
+ var kitManagerMethodName: String? = null
+ var foundInternal = false
+ var foundExternal = false
+ val objectList: MutableList = ArrayList()
+ for (obj in objects) {
+ objectList.add(obj)
+ }
+ for (i in stackTrace.indices) {
+ if (!isExternalApiInvocation(stackTrace[i])) {
+ foundInternal = true
+ }
+ if (foundInternal && !foundExternal) {
+ if (isExternalApiInvocation(stackTrace[i])) {
+ invokingApiMethodName = getApiName(stackTrace[i - 1])
+ foundExternal = true
+ }
+ }
+ if (stackTrace[i].className == "com.mparticle.kits.KitManagerImpl") {
+ kitManagerMethodName = getApiName(stackTrace[i])
+ }
+ }
+ val finalInvokingApiMethodName = invokingApiMethodName
+ val finalKitManagerMethodName = kitManagerMethodName
+ broadcast(object : SdkListenerRunnable {
+ override fun run(listener: SdkListener) {
+ listener.onKitApiCalled(
+ kitId,
+ methodName,
+ finalInvokingApiMethodName,
+ finalKitManagerMethodName,
+ objectList,
+ used
+ )
+ }
+ })
+ }
+
+ override fun onCompositeObjects(child: Any?, parent: Any?) {
+ broadcast(object : SdkGraphListenerRunnable {
+ override fun run(listener: GraphListener) {
+ listener.onCompositeObjects(child, parent)
+ }
+ })
+ }
+
+ override fun onThreadMessage(handlerName: String, msg: Message, onNewThread: Boolean) {
+ var stackTrace: Array? = null
+ if (!onNewThread) {
+ stackTrace = Thread.currentThread().stackTrace
+ }
+ val finalStackTrace = stackTrace
+ broadcast(object : SdkGraphListenerRunnable {
+ override fun run(listener: GraphListener) {
+ listener.onThreadMessage(handlerName, msg, onNewThread, finalStackTrace)
+ }
+ })
+ }
+
+ override fun onEntityStored(
+ primaryKey: Long, tableName: String, contentValues: ContentValues?
+ ) {
+ onCompositeObjects(contentValues, tableName + primaryKey)
+ val jsonObject = JSONObject()
+ var table: SdkListener.DatabaseTable? = null
+ table = try {
+ tableName.uppercase(Locale.getDefault()).let { SdkListener.DatabaseTable.valueOf(it) }
+ } catch (ex: IllegalArgumentException) {
+ SdkListener.DatabaseTable.UNKNOWN
+ }
+ if (contentValues != null) {
+ for ((key, value) in contentValues.valueSet()) {
+ try {
+ if (value == null) {
+ jsonObject.put(key, JSONObject.NULL)
+ } else {
+ jsonObject.put(key, value)
+ }
+ } catch (e: JSONException) {
+ e.printStackTrace()
+ }
+ }
+ }
+ val finalTable = table
+ broadcast(object : SdkListenerRunnable {
+ override fun run(listener: SdkListener) {
+ finalTable?.let { listener.onEntityStored(it, primaryKey, jsonObject) }
+ }
+ })
+ }
+
+ override fun onNetworkRequestStarted(
+ type: SdkListener.Endpoint, url: String, body: JSONObject?, vararg objects: Any
+ ) {
+ for (obj in objects) {
+ onCompositeObjects(obj, body)
+ }
+ val objectList: MutableList = ArrayList()
+ for (obj in objects) {
+ objectList.add(obj)
+ }
+ broadcast(object : SdkListenerRunnable {
+ override fun run(listener: SdkListener) {
+ listener.onNetworkRequestStarted(type, url, body ?: JSONObject())
+ }
+ })
+ }
+
+ override fun onNetworkRequestFinished(
+ type: SdkListener.Endpoint, url: String, response: JSONObject?, responseCode: Int
+ ) {
+
+ broadcast(object : SdkListenerRunnable {
+ override fun run(listener: SdkListener) {
+ listener.onNetworkRequestFinished(type, url, response, responseCode)
+ }
+ })
+ }
+
+ override fun onSessionUpdated(internalSession: InternalSession) {
+ broadcast(object : SdkListenerRunnable {
+ override fun run(listener: SdkListener) {
+ listener.onSessionUpdated(InternalSession(internalSession))
+ }
+ })
+ }
+
+ override fun onKitDetected(kitId: Int) {
+ broadcast(object : SdkListenerRunnable {
+ override fun run(listener: SdkListener) {
+ listener.onKitDetected(kitId)
+ }
+ })
+ }
+
+ override fun onKitConfigReceived(kitId: Int, configuration: String?) {
+ var jsonObject = JSONObject()
+ try {
+ jsonObject = JSONObject(configuration)
+ } catch (e: JSONException) {
+ }
+ val jsonConfig = jsonObject
+ broadcast(object : SdkListenerRunnable {
+ override fun run(listener: SdkListener) {
+ listener.onKitConfigReceived(kitId, jsonConfig)
+ }
+ })
+ }
+
+ override fun onKitExcluded(kitId: Int, reason: String?) {
+ broadcast(object : SdkListenerRunnable {
+ override fun run(listener: SdkListener) {
+ listener.onKitExcluded(kitId, reason)
+ }
+ })
+ }
+
+ override fun onKitStarted(kitId: Int) {
+ broadcast(object : SdkListenerRunnable {
+ override fun run(listener: SdkListener) {
+ listener.onKitStarted(kitId)
+ }
+ })
+ }
+
+ override fun onAliasRequestFinished(aliasResponse: AliasResponse?) {
+ broadcast(object : SdkListenerRunnable {
+ override fun run(listener: SdkListener) {
+ listener.onAliasRequestFinished(aliasResponse)
+ }
+ })
+ }
+
+ private fun broadcast(runnable: SdkListenerRunnable) {
+ for (listenerRef in ArrayList(sdkListeners)) {
+ val listener = listenerRef.get()
+ if (listener == null) {
+ sdkListeners.remove(listenerRef)
+ } else {
+ runnable.run(listener)
+ }
+ }
+ }
+
+ private fun broadcast(runnable: SdkGraphListenerRunnable) {
+ for (listenerRef in ArrayList(graphListeners)) {
+ val listener = listenerRef.get()
+ if (listener == null) {
+ graphListeners.remove(listenerRef)
+ } else {
+ runnable.run(listener)
+ }
+ }
+ }
+
+ internal interface SdkListenerRunnable {
+ fun run(listener: SdkListener)
+ }
+
+ internal interface SdkGraphListenerRunnable {
+ fun run(listener: GraphListener)
+ }
+
+ private fun getApiName(element: StackTraceElement): String {
+ val classNameString = getClassName(element.className, element.methodName)
+ return getApiFormattedName(classNameString, element.methodName)
+ }
+
+ private fun getClassName(className: String, methodName: String): String {
+ val packageNames =
+ className.split("\\.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
+ val simpleClassName = packageNames[packageNames.size - 1]
+ if (isObfuscated(simpleClassName)) {
+ try {
+ val superClasses: MutableList> = ArrayList()
+ val clazz = Class.forName(className) // nosemgrep
+ superClasses.add(clazz.superclass)
+ for (interfce in clazz.interfaces) {
+ superClasses.add(interfce)
+ }
+ for (superClass in superClasses) {
+ for (method in superClass.methods) {
+ if (method.name == methodName) {
+ val superClassName = getClassName(superClass.name, methodName)
+ if (!isObfuscated(superClassName)) {
+ return superClassName
+ }
+ }
+ }
+ }
+ } catch (e: ClassNotFoundException) {
+ e.printStackTrace()
+ }
+ }
+ return simpleClassName
+ }
+
+ private fun isObfuscated(className: String): Boolean {
+ return Character.isLowerCase(className.toCharArray()[0]) && className.length <= 3
+ }
+
+ private fun isExternalApiInvocation(element: StackTraceElement): Boolean {
+ return !element.className.startsWith("com.mparticle") || (element.className.startsWith(
+ context.applicationContext.packageName
+ ) && context.applicationContext.packageName.length > 1)
+ }
+
+ private fun hasListeners(): Boolean {
+ return (instance?.sdkListeners?.size ?: 0) > 0 || (instance?.graphListeners?.size ?: 0) > 0
+ }
+
+ companion object {
+ private var instance: InternalListenerManager? = null
+ private const val INTERNAL_LISTENER_PROP = "debug.mparticle.listener"
+
+ @JvmStatic
+ fun start(context: Context?): InternalListenerManager? {
+ val canRun = MPUtility.isAppDebuggable(context) || context?.packageName == MPUtility.getProp(INTERNAL_LISTENER_PROP)
+ if (instance == null && context != null && canRun) {
+ instance = InternalListenerManager(context.applicationContext)
+ }
+ return instance
+ }
+
+ @JvmStatic
+ val isEnabled: Boolean
+ get() = instance != null && instance?.hasListeners() == true
+
+ @JvmStatic
+ val listener: InternalListener
+ get() = instance?.let {
+ if (isEnabled) it else InternalListener.EMPTY
+ } ?: InternalListener.EMPTY
+
+ fun getApiFormattedName(className: String?, methodName: String?): String {
+ return StringBuilder().append(className).append(".").append(methodName).append("()")
+ .toString()
+ }
+ }
+}
diff --git a/android-core/src/test/kotlin/com/mparticle/internal/listeners/InternalListenerManagerTest.kt b/android-core/src/test/kotlin/com/mparticle/internal/listeners/InternalListenerManagerTest.kt
index 35190159c..b384c5b6e 100644
--- a/android-core/src/test/kotlin/com/mparticle/internal/listeners/InternalListenerManagerTest.kt
+++ b/android-core/src/test/kotlin/com/mparticle/internal/listeners/InternalListenerManagerTest.kt
@@ -21,30 +21,30 @@ class InternalListenerManagerTest {
PowerMockito.mockStatic(MPUtility::class.java)
Mockito.`when`(MPUtility.isDevEnv()).thenReturn(true)
Mockito.`when`(MPUtility.getProp(Mockito.anyString())).thenReturn(mockContext.packageName)
- Assert.assertNotNull(InternalListenerManager.getListener())
- Assert.assertEquals(InternalListenerManager.getListener(), InternalListener.EMPTY)
- Assert.assertFalse(InternalListenerManager.isEnabled())
+ Assert.assertNotNull(InternalListenerManager.listener)
+ Assert.assertEquals(InternalListenerManager.listener, InternalListener.EMPTY)
+ Assert.assertFalse(InternalListenerManager.isEnabled)
mockContext.isDebuggable = true
val manager = InternalListenerManager.start(mockContext)
Assert.assertNotNull(manager)
// Manager is started, but should still be a brick until an SdkListener is added.
- Assert.assertNotNull(InternalListenerManager.getListener())
- Assert.assertEquals(InternalListenerManager.getListener(), InternalListener.EMPTY)
- Assert.assertFalse(InternalListenerManager.isEnabled())
+ Assert.assertNotNull(InternalListenerManager.listener)
+ Assert.assertEquals(InternalListenerManager.listener, InternalListener.EMPTY)
+ Assert.assertFalse(InternalListenerManager.isEnabled)
val listener = SdkListener()
manager?.addListener(listener)
// Manager should now be active, since a listener was added.
- Assert.assertNotNull(InternalListenerManager.getListener())
- Assert.assertNotEquals(InternalListenerManager.getListener(), InternalListener.EMPTY)
- Assert.assertTrue(InternalListenerManager.isEnabled())
+ Assert.assertNotNull(InternalListenerManager.listener)
+ Assert.assertNotEquals(InternalListenerManager.listener, InternalListener.EMPTY)
+ Assert.assertTrue(InternalListenerManager.isEnabled)
manager?.removeListener(listener)
// Manager should go back to being a brick, since it's listener was removed.
- Assert.assertNotNull(InternalListenerManager.getListener())
- Assert.assertEquals(InternalListenerManager.getListener(), InternalListener.EMPTY)
- Assert.assertFalse(InternalListenerManager.isEnabled())
+ Assert.assertNotNull(InternalListenerManager.listener)
+ Assert.assertEquals(InternalListenerManager.listener, InternalListener.EMPTY)
+ Assert.assertFalse(InternalListenerManager.isEnabled)
}
@Test
@@ -57,9 +57,9 @@ class InternalListenerManagerTest {
val manager = InternalListenerManager.start(context)
// B rick instance of InternalListenerManager should act like a brick.
- Assert.assertNotNull(InternalListenerManager.getListener())
- Assert.assertEquals(InternalListenerManager.getListener(), InternalListener.EMPTY)
- Assert.assertFalse(InternalListenerManager.isEnabled())
+ Assert.assertNotNull(InternalListenerManager.listener)
+ Assert.assertEquals(InternalListenerManager.listener, InternalListener.EMPTY)
+ Assert.assertFalse(InternalListenerManager.isEnabled)
Assert.assertNull(manager)
}