Skip to content

Commit 2ddfe8e

Browse files
committed
Convert to Kotlin add timeouts
1 parent 82a4cd0 commit 2ddfe8e

4 files changed

Lines changed: 154 additions & 169 deletions

File tree

android-core/src/main/java/com/mparticle/InstallReferrerHelper.java

Lines changed: 0 additions & 169 deletions
This file was deleted.
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
package com.mparticle
2+
3+
import android.content.Context
4+
import android.os.Looper
5+
import com.mparticle.internal.Constants
6+
import com.mparticle.internal.Logger
7+
import java.lang.reflect.Proxy
8+
9+
/**
10+
* Helper class to fetch install referrer via reflection.
11+
*
12+
* To test the install referrer functionality:
13+
*
14+
* 1. Set test application `applicationId` to a real app's Play Store package name, e.g. "com.medium.reader".
15+
*
16+
* 2. Ensure the app is fully uninstalled from the test device:
17+
* `adb uninstall com.medium.reader`
18+
*
19+
* 3. Broadcast the test referrer to the Play Store. This will open the Play Store app:
20+
* `adb shell "am start -a android.intent.action.VIEW -d 'https://play.google.com/store/apps/details?id=com.medium.reader&referrer=utm_source%3Dgoogle%26utm_medium%3Dcpc%26utm_term%3Drunning%252Bshoes%26utm_content%3Dcontent%26utm_campaign%3Dpromo'"`
21+
*
22+
* 4. Install the debug version of your test app.
23+
*
24+
* 5. Launch the app and check the logs for the referrer:
25+
* `adb logcat -d | grep 'Install Referrer received'`
26+
*
27+
* 6. Verify the referrer is logged in the test app's logs by checking for the utm_* parameters passed in during step 3.
28+
* e.g. `Install Referrer received: utm_source=google&utm_medium=cpc&utm_term=running%2Bshoes&utm_content=content&utm_campaign=promo`
29+
*
30+
* @see <a href="https://developer.android.com/reference/com/android/installreferrer/api/InstallReferrerClient">InstallReferrerClient</a>
31+
* @see <a href="https://medium.com/@madicdjordje/how-to-test-the-play-store-install-referrer-api-78a63d59945b">How to Test the Play Store Install Referrer API</a>
32+
*/
33+
object InstallReferrerHelper {
34+
35+
private const val INSTALL_REFERRER_CLIENT_CLASS = "com.android.installreferrer.api.InstallReferrerClient"
36+
private const val INSTALL_REFERRER_STATE_LISTENER_CLASS = "com.android.installreferrer.api.InstallReferrerStateListener"
37+
private const val REFERRER_DETAILS_CLASS = "com.android.installreferrer.api.ReferrerDetails"
38+
private const val INSTALL_REFERRER_RESPONSE_OK = 0
39+
40+
@JvmStatic
41+
fun getInstallReferrer(context: Context): String? {
42+
return context.getSharedPreferences(Constants.PREFS_FILE, Context.MODE_PRIVATE)
43+
.getString(Constants.PrefKeys.INSTALL_REFERRER, null)
44+
}
45+
46+
@JvmStatic
47+
fun setInstallReferrer(context: Context, referrer: String?) {
48+
val preferences = context.getSharedPreferences(Constants.PREFS_FILE, Context.MODE_PRIVATE)
49+
preferences.edit().putString(Constants.PrefKeys.INSTALL_REFERRER, referrer).apply()
50+
MParticle.getInstance()?.installReferrerUpdated()
51+
}
52+
53+
@JvmStatic
54+
fun fetchInstallReferrer(context: Context, callback: InstallReferrerCallback) {
55+
if (getInstallReferrer(context) != null) return
56+
57+
val runnable = Runnable {
58+
try {
59+
val clientClass = Class.forName(INSTALL_REFERRER_CLIENT_CLASS)
60+
val listenerClass = Class.forName(INSTALL_REFERRER_STATE_LISTENER_CLASS)
61+
val referrerDetailsClass = Class.forName(REFERRER_DETAILS_CLASS)
62+
63+
val builder = clientClass.getMethod("newBuilder", Context::class.java).invoke(null, context)
64+
if (builder == null) {
65+
Logger.warning("InstallReferrerClient.newBuilder() returned null")
66+
callback.onFailed()
67+
return@Runnable
68+
}
69+
70+
val builtClient = builder.javaClass.getMethod("build").invoke(builder)
71+
72+
val listener = Proxy.newProxyInstance(
73+
listenerClass.classLoader,
74+
arrayOf(listenerClass)
75+
) { proxy, method, args ->
76+
when (method.name) {
77+
"equals" -> return@newProxyInstance proxy === args?.get(0)
78+
"hashCode" -> return@newProxyInstance System.identityHashCode(proxy)
79+
"toString" -> return@newProxyInstance "InstallReferrerListenerProxy"
80+
"onInstallReferrerSetupFinished" -> {
81+
val responseCode = args?.get(0) as? Int ?: -1
82+
83+
if (responseCode == INSTALL_REFERRER_RESPONSE_OK) {
84+
try {
85+
val referrerDetails = clientClass.getMethod("getInstallReferrer").invoke(builtClient)
86+
if (referrerDetails != null) {
87+
val referrerUrl = referrerDetailsClass.getMethod("getInstallReferrer")
88+
.invoke(referrerDetails) as? String
89+
Logger.debug("Install Referrer received: $referrerUrl")
90+
callback.onReceived(referrerUrl)
91+
} else {
92+
callback.onFailed()
93+
}
94+
clientClass.getMethod("endConnection").invoke(builtClient)
95+
} catch (e: Exception) {
96+
Logger.warning("Reflection error getting install referrer: ${e.message}")
97+
callback.onFailed()
98+
}
99+
} else {
100+
callback.onFailed()
101+
}
102+
}
103+
}
104+
null
105+
}
106+
107+
val startConnectionMethod = clientClass.methods.firstOrNull {
108+
it.name == "startConnection" && it.parameterTypes.size == 1
109+
}
110+
111+
if (startConnectionMethod == null) {
112+
Logger.warning("Could not find startConnection method via reflection")
113+
callback.onFailed()
114+
return@Runnable
115+
}
116+
117+
startConnectionMethod.invoke(builtClient, listener)
118+
119+
} catch (e: ClassNotFoundException) {
120+
Logger.warning("InstallReferrer library not present")
121+
callback.onFailed()
122+
} catch (e: Exception) {
123+
Logger.error("Error fetching install referrer: ${e.message}")
124+
callback.onFailed()
125+
}
126+
}
127+
128+
try {
129+
if (Looper.getMainLooper() == Looper.myLooper()) {
130+
Thread(runnable).start()
131+
} else {
132+
runnable.run()
133+
}
134+
} catch (e: Exception) {
135+
callback.onFailed()
136+
}
137+
}
138+
139+
interface InstallReferrerCallback {
140+
fun onReceived(installReferrer: String?)
141+
fun onFailed()
142+
}
143+
}

testutils/src/main/java/com/mparticle/testutils/BaseAbstractTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,13 @@
3535
import org.junit.Before;
3636
import org.junit.BeforeClass;
3737
import org.junit.Rule;
38+
import org.junit.rules.Timeout;
3839

3940
import java.util.ArrayList;
4041
import java.util.List;
4142
import java.util.Random;
4243
import java.util.concurrent.CountDownLatch;
44+
import java.util.concurrent.TimeUnit;
4345

4446
public abstract class BaseAbstractTest {
4547
protected MockServer mServer;
@@ -50,6 +52,8 @@ public abstract class BaseAbstractTest {
5052
protected static Long mStartingMpid;
5153
private static final String defaultDatabaseName = MParticleDatabaseHelper.getDbName();
5254

55+
@Rule
56+
public Timeout timeout = new Timeout(60, TimeUnit.SECONDS);
5357
@Rule
5458
public CaptureLogcatOnFailingTest captureFailingTestLogcat = new CaptureLogcatOnFailingTest();
5559

testutils/src/main/java/com/mparticle/testutils/BaseCleanInstallEachTest.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
package com.mparticle.testutils;
22

33
import org.junit.Before;
4+
import org.junit.Rule;
5+
import org.junit.rules.Timeout;
6+
7+
import java.util.concurrent.TimeUnit;
48

59
/**
610
* Base class that will replicate the scenario or an app that has started, but has not called
711
* MParticle.start(). This base class is useful for testing initialization behavior.
812
*/
913
abstract public class BaseCleanInstallEachTest extends BaseAbstractTest {
1014

15+
@Rule
16+
public Timeout timeout = new Timeout(60, TimeUnit.SECONDS);
17+
1118
@Before
1219
public void beforeBase() throws Exception {
1320

0 commit comments

Comments
 (0)