Skip to content

Commit 72e3d1d

Browse files
Move optional synchronous user agent fetch out of start up (#1181)
* Move synchronous user agent fetch out of start up * move sync call to main dispatcher * Update CustomBranchApp.java * Update DeviceInfo.java * Update DeviceInfo.java * add else case to add object
1 parent 1b6bb47 commit 72e3d1d

File tree

8 files changed

+276
-111
lines changed

8 files changed

+276
-111
lines changed

Branch-SDK-TestBed/src/main/java/io/branch/branchandroidtestbed/MainActivity.java

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -654,6 +654,35 @@ protected void onStart() {
654654
Branch.getInstance().addFacebookPartnerParameterWithName("em", getHashedValue("[email protected]"));
655655
Branch.getInstance().addFacebookPartnerParameterWithName("ph", getHashedValue("6516006060"));
656656
Log.d("BranchSDK_Tester", "initSession");
657+
658+
initSessionsWithTests();
659+
660+
// Branch integration validation: Validate Branch integration with your app
661+
// NOTE : The below method will run few checks for verifying correctness of the Branch integration.
662+
// Please look for "BranchSDK_Doctor" in the logcat to see the results.
663+
// IMP : Do not make this call in your production app
664+
665+
//IntegrationValidator.validate(MainActivity.this);
666+
}
667+
668+
669+
private void initSessionsWithTests() {
670+
boolean testUserAgent = true;
671+
userAgentTests(testUserAgent, 10);
672+
}
673+
674+
// Enqueue several v2 events prior to init to simulate worst timing conditions for user agent fetch
675+
// TODO Add to automation.
676+
// Check that all events up to Event N-1 complete with user agent string.
677+
private void userAgentTests(boolean userAgentSync, int n) {
678+
Branch.setIsUserAgentSync(userAgentSync);
679+
Log.i("BranchSDK_Tester", "Beginning stress tests with IsUserAgentSync" + Branch.getIsUserAgentSync());
680+
681+
for (int i = 0; i < n; i++) {
682+
BranchEvent event = new BranchEvent("Event " + i);
683+
event.logEvent(this);
684+
}
685+
657686
Branch.sessionBuilder(this).withCallback(new Branch.BranchUniversalReferralInitListener() {
658687
@Override
659688
public void onInitFinished(BranchUniversalObject branchUniversalObject, LinkProperties linkProperties, BranchError error) {
@@ -677,17 +706,8 @@ public void onInitFinished(BranchUniversalObject branchUniversalObject, LinkProp
677706
// QA purpose only
678707
// TrackingControlTestRoutines.runTrackingControlTest(MainActivity.this);
679708
// BUOTestRoutines.TestBUOFunctionalities(MainActivity.this);
680-
681709
}
682710
}).withData(this.getIntent().getData()).init();
683-
684-
// Branch integration validation: Validate Branch integration with your app
685-
// NOTE : The below method will run few checks for verifying correctness of the Branch integration.
686-
// Please look for "BranchSDK_Doctor" in the logcat to see the results.
687-
// IMP : Do not make this call in your production app
688-
689-
//IntegrationValidator.validate(MainActivity.this);
690-
691711
}
692712

693713
@Override
@@ -704,8 +724,6 @@ public void onInitFinished(JSONObject referringParams, BranchError error) {
704724
}
705725
}
706726
}).reInit();
707-
708-
709727
}
710728

711729
@Override
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package io.branch.coroutines
2+
3+
import android.content.Context
4+
import android.text.TextUtils
5+
import android.webkit.WebSettings
6+
import android.webkit.WebView
7+
import io.branch.referral.Branch
8+
import io.branch.referral.BranchLogger.e
9+
import io.branch.referral.BranchLogger.v
10+
import kotlinx.coroutines.Dispatchers
11+
import kotlinx.coroutines.sync.Mutex
12+
import kotlinx.coroutines.sync.withLock
13+
import kotlinx.coroutines.withContext
14+
15+
val mutex = Mutex()
16+
17+
/**
18+
* Returns the user agent string on a background thread via static class WebSettings
19+
* This is the default behavior.
20+
*
21+
* Use a mutex to ensure only one is executed at a time.
22+
* Successive calls will return the cached value.
23+
*
24+
* For performance, this is called at the end of the init, or while awaiting init if enqueued prior.
25+
*/
26+
suspend fun getUserAgentAsync(context: Context): String? {
27+
return withContext(Dispatchers.Default) {
28+
mutex.withLock {
29+
var result: String? = null
30+
31+
if (!TextUtils.isEmpty(Branch._userAgentString)) {
32+
v("UserAgent cached " + Branch._userAgentString)
33+
result = Branch._userAgentString
34+
}
35+
else {
36+
try {
37+
v("Begin getUserAgentAsync " + Thread.currentThread())
38+
result = WebSettings.getDefaultUserAgent(context)
39+
v("End getUserAgentAsync " + Thread.currentThread() + " " + result)
40+
}
41+
catch (exception: Exception) {
42+
e("Failed to retrieve userAgent string. " + exception.message)
43+
}
44+
}
45+
46+
result
47+
}
48+
}
49+
}
50+
51+
/**
52+
* Returns the user agent string on the main thread via WebView instance.
53+
* Use when facing errors with the WebSettings static API.
54+
* https://bugs.chromium.org/p/chromium/issues/detail?id=1279562
55+
* https://bugs.chromium.org/p/chromium/issues/detail?id=1271617
56+
*
57+
*
58+
* Because there is only one main thread, this function will only execute one at a time.
59+
* Successive calls will return the cached value.
60+
*/
61+
suspend fun getUserAgentSync(context: Context): String?{
62+
return withContext(Dispatchers.Main){
63+
var result: String? = null
64+
65+
if(!TextUtils.isEmpty(Branch._userAgentString)){
66+
v("UserAgent cached " + Branch._userAgentString)
67+
result = Branch._userAgentString
68+
}
69+
else {
70+
try {
71+
v("Begin getUserAgentSync " + Thread.currentThread())
72+
val w = WebView(context)
73+
result = w.settings.userAgentString
74+
w.destroy()
75+
v("End getUserAgentSync " + Thread.currentThread() + " " + result)
76+
}
77+
catch (ex: Exception) {
78+
e("Failed to retrieve userAgent string. " + ex.message)
79+
}
80+
}
81+
82+
result
83+
}
84+
}

Branch-SDK/src/main/java/io/branch/referral/Branch.java

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ public class Branch {
196196
/**
197197
* Package private user agent string cached to save on repeated queries
198198
*/
199-
static String _userAgentString = "";
199+
public static String _userAgentString = "";
200200

201201
/* Json object containing key-value pairs for debugging deep linking */
202202
private JSONObject deeplinkDebugParams_;
@@ -355,11 +355,6 @@ synchronized private static Branch initBranchSDK(@NonNull Context context, Strin
355355
branchReferral_.setActivityLifeCycleObserver((Application) context);
356356
}
357357

358-
// Cache the user agent from a webview instance if needed
359-
if(userAgentSync && DeviceInfo.getInstance() != null){
360-
DeviceInfo.getInstance().getUserAgentStringSync(context);
361-
}
362-
363358
return branchReferral_;
364359
}
365360

@@ -778,7 +773,11 @@ public static boolean isReferringLinkAttributionForPreinstalledAppsEnabled() {
778773
public static void setIsUserAgentSync(boolean sync){
779774
userAgentSync = sync;
780775
}
781-
776+
777+
public static boolean getIsUserAgentSync(){
778+
return userAgentSync;
779+
}
780+
782781
/*
783782
* <p>Closes the current session. Should be called by on getting the last actvity onStop() event.
784783
* </p>

Branch-SDK/src/main/java/io/branch/referral/DeviceInfo.java

Lines changed: 91 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,23 @@
11
package io.branch.referral;
22

3+
import static android.content.Context.UI_MODE_SERVICE;
4+
import static io.branch.referral.PrefHelper.NO_STRING_VALUE;
5+
36
import android.app.UiModeManager;
47
import android.content.Context;
58
import android.content.res.Configuration;
6-
import android.os.Build;
7-
import android.os.Handler;
8-
import android.os.Looper;
99
import android.text.TextUtils;
1010
import android.util.DisplayMetrics;
11-
import android.util.Log;
12-
import android.webkit.WebSettings;
13-
import android.webkit.WebView;
11+
12+
import androidx.annotation.NonNull;
1413

1514
import org.json.JSONException;
1615
import org.json.JSONObject;
1716

18-
import static android.content.Context.UI_MODE_SERVICE;
19-
import static io.branch.referral.PrefHelper.NO_STRING_VALUE;
20-
21-
import java.util.Iterator;
22-
import java.util.Objects;
17+
import io.branch.coroutines.DeviceSignalsKt;
18+
import kotlin.coroutines.Continuation;
19+
import kotlin.coroutines.CoroutineContext;
20+
import kotlin.coroutines.EmptyCoroutineContext;
2321

2422
/**
2523
* <p>
@@ -215,7 +213,8 @@ void updateRequestWithV2Params(ServerRequest serverRequest, PrefHelper prefHelpe
215213
userDataObj.put(Defines.Jsonkey.AppVersion.getKey(), getAppVersion());
216214
userDataObj.put(Defines.Jsonkey.SDK.getKey(), "android");
217215
userDataObj.put(Defines.Jsonkey.SdkVersion.getKey(), Branch.getSdkVersionNumber());
218-
userDataObj.put(Defines.Jsonkey.UserAgent.getKey(), getDefaultBrowserAgent(context_));
216+
217+
setPostUserAgent(userDataObj);
219218

220219
if (serverRequest instanceof ServerRequestGetLATD) {
221220
userDataObj.put(Defines.Jsonkey.LATDAttributionWindow.getKey(),
@@ -236,6 +235,86 @@ void updateRequestWithV2Params(ServerRequest serverRequest, PrefHelper prefHelpe
236235
}
237236
}
238237

238+
/**
239+
* Method to append the user agent string to the POST request body's user_data object
240+
* If the user agent string is empty, either because it was not obtained asynchronously
241+
* or on time, query it synchronously.
242+
* @param userDataObj
243+
*/
244+
private void setPostUserAgent(final JSONObject userDataObj) {
245+
BranchLogger.v("setPostUserAgent " + Thread.currentThread().getName());
246+
try {
247+
if (!TextUtils.isEmpty(Branch._userAgentString)) {
248+
BranchLogger.v("userAgent was cached: " + Branch._userAgentString);
249+
250+
userDataObj.put(Defines.Jsonkey.UserAgent.getKey(), Branch._userAgentString);
251+
252+
Branch.getInstance().requestQueue_.unlockProcessWait(ServerRequest.PROCESS_WAIT_LOCK.USER_AGENT_STRING_LOCK);
253+
Branch.getInstance().requestQueue_.processNextQueueItem("setPostUserAgent");
254+
}
255+
else if (Branch.userAgentSync) {
256+
// If user agent sync is false, then the async coroutine is executed instead but may not have finished yet.
257+
BranchLogger.v("Start invoking getUserAgentSync from thread " + Thread.currentThread().getName());
258+
DeviceSignalsKt.getUserAgentSync(context_, new Continuation<String>() {
259+
@NonNull
260+
@Override
261+
public CoroutineContext getContext() {
262+
return EmptyCoroutineContext.INSTANCE;
263+
}
264+
265+
@Override
266+
public void resumeWith(@NonNull Object o) {
267+
if (o != null) {
268+
Branch._userAgentString = (String) o;
269+
BranchLogger.v("onUserAgentStringFetchFinished getUserAgentSync resumeWith releasing lock");
270+
271+
try {
272+
userDataObj.put(Defines.Jsonkey.UserAgent.getKey(), Branch._userAgentString);
273+
}
274+
catch (JSONException e) {
275+
BranchLogger.w("Caught JSONException " + e.getMessage());
276+
}
277+
}
278+
279+
Branch.getInstance().requestQueue_.unlockProcessWait(ServerRequest.PROCESS_WAIT_LOCK.USER_AGENT_STRING_LOCK);
280+
Branch.getInstance().requestQueue_.processNextQueueItem("onUserAgentStringFetchFinished");
281+
}
282+
});
283+
}
284+
// In cases where v2 events objects are enqueued before an init, this will execute first.
285+
else {
286+
DeviceSignalsKt.getUserAgentAsync(context_, new Continuation<String>() {
287+
@NonNull
288+
@Override
289+
public CoroutineContext getContext() {
290+
return EmptyCoroutineContext.INSTANCE;
291+
}
292+
293+
@Override
294+
public void resumeWith(@NonNull Object o) {
295+
if (o != null) {
296+
Branch._userAgentString = (String) o;
297+
BranchLogger.v("onUserAgentStringFetchFinished getUserAgentAsync resumeWith releasing lock");
298+
299+
try {
300+
userDataObj.put(Defines.Jsonkey.UserAgent.getKey(), Branch._userAgentString);
301+
}
302+
catch (JSONException e) {
303+
BranchLogger.w("Caught JSONException " + e.getMessage());
304+
}
305+
}
306+
307+
Branch.getInstance().requestQueue_.unlockProcessWait(ServerRequest.PROCESS_WAIT_LOCK.USER_AGENT_STRING_LOCK);
308+
Branch.getInstance().requestQueue_.processNextQueueItem("getUserAgentAsync resumeWith");
309+
}
310+
});
311+
}
312+
}
313+
catch (Exception exception){
314+
BranchLogger.w("Caught exception trying to set userAgent " + exception.getMessage());
315+
}
316+
}
317+
239318
/**
240319
* get the package name for the this application
241320
*
@@ -288,63 +367,6 @@ public String getOsName() {
288367
return systemObserver_.getOS(context_);
289368
}
290369

291-
292-
/**
293-
* Returns the browser's user agent string
294-
* PRS : User agent is checked only from api-17
295-
* @param context
296-
* @return user agent string
297-
*/
298-
String getDefaultBrowserAgent(final Context context) {
299-
if(!TextUtils.isEmpty(Branch._userAgentString)) {
300-
return Branch._userAgentString;
301-
}
302-
303-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
304-
try {
305-
BranchLogger.v("Retrieving user agent string from WebSettings");
306-
Branch._userAgentString = WebSettings.getDefaultUserAgent(context);
307-
}
308-
catch (Exception exception) {
309-
BranchLogger.v(exception.getMessage());
310-
// A known Android issue. Webview packages are not accessible while any updates for chrome is in progress.
311-
// https://bugs.chromium.org/p/chromium/issues/detail?id=506369
312-
}
313-
}
314-
return Branch._userAgentString;
315-
}
316-
317-
/**
318-
* Must be called from the main thread
319-
* Some devices appear to crash when accessing chromium through the Android framework statics
320-
* Suggested alternative is to use a webview instance
321-
* https://bugs.chromium.org/p/chromium/issues/detail?id=1279562
322-
* https://bugs.chromium.org/p/chromium/issues/detail?id=1271617
323-
**/
324-
String getUserAgentStringSync(final Context context){
325-
if(!TextUtils.isEmpty(Branch._userAgentString)) {
326-
return Branch._userAgentString;
327-
}
328-
329-
new Handler(Looper.getMainLooper()).post(new Runnable() {
330-
@Override
331-
public void run() {
332-
try {
333-
BranchLogger.v("Running WebView initialization for user agent on thread " + Thread.currentThread());
334-
WebView w = new WebView(context);
335-
Branch._userAgentString = w.getSettings().getUserAgentString();
336-
w.destroy();
337-
}
338-
catch (Exception e) {
339-
BranchLogger.v(e.getMessage());
340-
}
341-
342-
}
343-
});
344-
345-
return Branch._userAgentString;
346-
}
347-
348370
/**
349371
* Concrete SystemObserver implementation
350372
*/
@@ -364,6 +386,4 @@ SystemObserver getSystemObserver() {
364386
public static boolean isNullOrEmptyOrBlank(String str) {
365387
return TextUtils.isEmpty(str) || str.equals(SystemObserver.BLANK);
366388
}
367-
368-
369389
}

Branch-SDK/src/main/java/io/branch/referral/ServerRequest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ public abstract class ServerRequest {
4242
private final Context context_;
4343

4444
// Various process wait locks for Branch server request
45-
enum PROCESS_WAIT_LOCK {
46-
SDK_INIT_WAIT_LOCK, GAID_FETCH_WAIT_LOCK, INTENT_PENDING_WAIT_LOCK, USER_SET_WAIT_LOCK, INSTALL_REFERRER_FETCH_WAIT_LOCK
45+
public enum PROCESS_WAIT_LOCK {
46+
SDK_INIT_WAIT_LOCK, GAID_FETCH_WAIT_LOCK, INTENT_PENDING_WAIT_LOCK, USER_SET_WAIT_LOCK, INSTALL_REFERRER_FETCH_WAIT_LOCK, USER_AGENT_STRING_LOCK
4747
}
4848

4949
// Set for holding any active wait locks

0 commit comments

Comments
 (0)