From e080980733682e3f28b96ec6d39345d2d8261753 Mon Sep 17 00:00:00 2001 From: Nipun Singh <88689850+nsingh-branch@users.noreply.github.com> Date: Fri, 21 Jul 2023 15:00:31 -0700 Subject: [PATCH 1/4] [SDK-1689] Updated generateShortLinkSync to create short link when tracking is disabled (#1081) * Updated generateShortLinkSync and test * Updated test's name --- .../io/branch/referral/BranchApiTests.java | 4 +- .../main/java/io/branch/referral/Branch.java | 40 ++++++++----------- 2 files changed, 18 insertions(+), 26 deletions(-) diff --git a/Branch-SDK/src/androidTest/java/io/branch/referral/BranchApiTests.java b/Branch-SDK/src/androidTest/java/io/branch/referral/BranchApiTests.java index 3508b36b7..2578e70ce 100644 --- a/Branch-SDK/src/androidTest/java/io/branch/referral/BranchApiTests.java +++ b/Branch-SDK/src/androidTest/java/io/branch/referral/BranchApiTests.java @@ -71,9 +71,9 @@ public void run() { } @Test - public void test00GetShortUrlSyncFailure() { + public void test00GetShortUrlSync() { String url = new BranchShortLinkBuilder(getTestContext()).getShortUrl(); - Assert.assertNull(url); + Assert.assertNotNull(url); } @Test diff --git a/Branch-SDK/src/main/java/io/branch/referral/Branch.java b/Branch-SDK/src/main/java/io/branch/referral/Branch.java index dc9b54328..b4e8ac266 100644 --- a/Branch-SDK/src/main/java/io/branch/referral/Branch.java +++ b/Branch-SDK/src/main/java/io/branch/referral/Branch.java @@ -1628,35 +1628,27 @@ public void cancelShareLinkDialog(boolean animateClose) { // PRIVATE FUNCTIONS private String generateShortLinkSync(ServerRequestCreateUrl req) { - if (trackingController.isTrackingDisabled()) { - return req.getLongUrl(); + ServerResponse response = null; + try { + int timeOut = prefHelper_.getTimeout() + 2000; // Time out is set to slightly more than link creation time to prevent any edge case + response = new GetShortLinkTask().execute(req).get(timeOut, TimeUnit.MILLISECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException ignore) { + } + String url = null; + if (req.isDefaultToLongUrl()) { + url = req.getLongUrl(); } - if (initState_ == SESSION_STATE.INITIALISED) { - ServerResponse response = null; + if (response != null && response.getStatusCode() == HttpURLConnection.HTTP_OK) { try { - int timeOut = prefHelper_.getTimeout() + 2000; // Time out is set to slightly more than link creation time to prevent any edge case - response = new GetShortLinkTask().execute(req).get(timeOut, TimeUnit.MILLISECONDS); - } catch (InterruptedException | ExecutionException | TimeoutException ignore) { - } - String url = null; - if (req.isDefaultToLongUrl()) { - url = req.getLongUrl(); - } - if (response != null && response.getStatusCode() == HttpURLConnection.HTTP_OK) { - try { - url = response.getObject().getString("url"); - if (req.getLinkPost() != null) { - linkCache_.put(req.getLinkPost(), url); - } - } catch (JSONException e) { - e.printStackTrace(); + url = response.getObject().getString("url"); + if (req.getLinkPost() != null) { + linkCache_.put(req.getLinkPost(), url); } + } catch (JSONException e) { + e.printStackTrace(); } - return url; - } else { - PrefHelper.Debug("Warning: User session has not been initialized"); } - return null; + return url; } private JSONObject convertParamsStringToDictionary(String paramString) { From 52c82fef194743d59ac654524545e4df71866d3b Mon Sep 17 00:00:00 2001 From: gdeluna-branch Date: Fri, 21 Jul 2023 16:14:55 -0700 Subject: [PATCH 2/4] fix:Convert GAID AsyncTask to coroutine (#1084) --- .../referral/BranchTestRequestUtil.java | 2 +- .../io/branch/coroutines/DataCoroutines.kt | 19 +++ .../main/java/io/branch/referral/Branch.java | 36 ++--- .../io/branch/referral/GAdsPrefetchTask.java | 136 ------------------ .../io/branch/referral/SystemObserver.java | 66 ++++++++- .../branch/referral/util/DependencyUtils.kt | 26 ++++ 6 files changed, 122 insertions(+), 163 deletions(-) create mode 100644 Branch-SDK/src/main/java/io/branch/coroutines/DataCoroutines.kt delete mode 100644 Branch-SDK/src/main/java/io/branch/referral/GAdsPrefetchTask.java create mode 100644 Branch-SDK/src/main/java/io/branch/referral/util/DependencyUtils.kt diff --git a/Branch-SDK/src/androidTest/java/io/branch/referral/BranchTestRequestUtil.java b/Branch-SDK/src/androidTest/java/io/branch/referral/BranchTestRequestUtil.java index 3c0dca7a7..da33ee57c 100644 --- a/Branch-SDK/src/androidTest/java/io/branch/referral/BranchTestRequestUtil.java +++ b/Branch-SDK/src/androidTest/java/io/branch/referral/BranchTestRequestUtil.java @@ -20,7 +20,7 @@ abstract class BranchTestRequestUtil { // can be pretty short because we mock remote interface and don't actually make async calls from the SDK public static final int TEST_REQUEST_TIMEOUT = 1000; - public static final int TEST_INIT_SESSION_TIMEOUT = 5000; + public static final int TEST_INIT_SESSION_TIMEOUT = 10000; // Dig out the variable for isStandardEvent from the BranchEvent object. protected boolean isStandardEvent(BranchEvent event) throws Exception { diff --git a/Branch-SDK/src/main/java/io/branch/coroutines/DataCoroutines.kt b/Branch-SDK/src/main/java/io/branch/coroutines/DataCoroutines.kt new file mode 100644 index 000000000..cfd61f2a0 --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/coroutines/DataCoroutines.kt @@ -0,0 +1,19 @@ +package io.branch.coroutines + +import android.content.Context +import com.google.android.gms.ads.identifier.AdvertisingIdClient +import io.branch.referral.PrefHelper +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +suspend fun getAdvertisingInfoObject(context: Context): AdvertisingIdClient.Info? { + return withContext(Dispatchers.Default) { + try { + AdvertisingIdClient.getAdvertisingIdInfo(context) + } + catch (exception: Exception) { + PrefHelper.Debug("getAdvertisingIdInfo exception: $exception") + null + } + } +} \ No newline at end of file diff --git a/Branch-SDK/src/main/java/io/branch/referral/Branch.java b/Branch-SDK/src/main/java/io/branch/referral/Branch.java index b4e8ac266..e1e5d0ad5 100644 --- a/Branch-SDK/src/main/java/io/branch/referral/Branch.java +++ b/Branch-SDK/src/main/java/io/branch/referral/Branch.java @@ -5,6 +5,10 @@ import static io.branch.referral.BranchPreinstall.getPreinstallSystemData; import static io.branch.referral.BranchUtil.isTestModeEnabled; import static io.branch.referral.PrefHelper.isValidBranchKey; +import static io.branch.referral.util.DependencyUtilsKt.classExists; +import static io.branch.referral.util.DependencyUtilsKt.galaxyStoreInstallReferrerClass; +import static io.branch.referral.util.DependencyUtilsKt.huaweiInstallReferrerClass; +import static io.branch.referral.util.DependencyUtilsKt.xiaomiInstallReferrerClass; import android.app.Activity; import android.app.Application; @@ -14,25 +18,19 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.annotation.StyleRes; - import android.os.Handler; import android.os.Looper; import android.text.TextUtils; -import android.view.View; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.core.app.ActivityCompat; import com.android.billingclient.api.Purchase; -import io.branch.referral.Defines.PreinstallKey; -import io.branch.referral.ServerRequestGetLATD.BranchLastAttributedTouchDataListener; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -41,7 +39,6 @@ import java.lang.ref.WeakReference; import java.net.HttpURLConnection; import java.net.URLEncoder; -import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -54,6 +51,8 @@ import java.util.concurrent.TimeoutException; import io.branch.indexing.BranchUniversalObject; +import io.branch.referral.Defines.PreinstallKey; +import io.branch.referral.ServerRequestGetLATD.BranchLastAttributedTouchDataListener; import io.branch.referral.network.BranchRemoteInterface; import io.branch.referral.network.BranchRemoteInterfaceUrlConnection; import io.branch.referral.util.BRANCH_STANDARD_EVENT; @@ -429,9 +428,6 @@ private Branch(@NonNull Context context) { branchPluginSupport_ = new BranchPluginSupport(context); branchQRCodeCache_ = new BranchQRCodeCache(context); requestQueue_ = ServerRequestQueue.getInstance(context); - if (!trackingController.isTrackingDisabled()) { // Do not get GAID when tracking is disabled - isGAParamsFetchInProgress_ = deviceInfo_.getSystemObserver().prefetchAdsParams(context,this); - } } /** @@ -1941,19 +1937,19 @@ void registerAppInit(@NonNull ServerRequestInitSession request, boolean ignoreWa request.addProcessWaitLock(ServerRequest.PROCESS_WAIT_LOCK.GOOGLE_INSTALL_REFERRER_FETCH_WAIT_LOCK); } - if (classExists("com.huawei.hms.ads.installreferrer.api.InstallReferrerClient") + if (classExists(huaweiInstallReferrerClass) && !StoreReferrerHuaweiAppGallery.hasBeenUsed) { waitingForHuaweiInstallReferrer = true; request.addProcessWaitLock(ServerRequest.PROCESS_WAIT_LOCK.HUAWEI_INSTALL_REFERRER_FETCH_WAIT_LOCK); } - if (classExists("com.sec.android.app.samsungapps.installreferrer.api.InstallReferrerClient") + if (classExists(galaxyStoreInstallReferrerClass) && !StoreReferrerSamsungGalaxyStore.hasBeenUsed) { waitingForSamsungInstallReferrer = true; request.addProcessWaitLock(ServerRequest.PROCESS_WAIT_LOCK.SAMSUNG_INSTALL_REFERRER_FETCH_WAIT_LOCK); } - if (classExists("com.miui.referrer.api.GetAppsReferrerClient") + if (classExists(xiaomiInstallReferrerClass) && !StoreReferrerXiaomiGetApps.hasBeenUsed) { waitingForXiaomiInstallReferrer = true; request.addProcessWaitLock(ServerRequest.PROCESS_WAIT_LOCK.XIAOMI_INSTALL_REFERRER_FETCH_WAIT_LOCK); @@ -2004,16 +2000,6 @@ void registerAppInit(@NonNull ServerRequestInitSession request, boolean ignoreWa r.callback_ = request.callback_; } } - - private boolean classExists(String className) { - try { - Class.forName(className); - return true; - } catch (ClassNotFoundException e) { - PrefHelper.Debug("Could not find " + className + ". If expected, import the dependency into your app."); - return false; - } - } ServerRequestInitSession getInstallOrOpenRequest(BranchReferralInitListener callback, boolean isAutoInitialization) { ServerRequestInitSession request; diff --git a/Branch-SDK/src/main/java/io/branch/referral/GAdsPrefetchTask.java b/Branch-SDK/src/main/java/io/branch/referral/GAdsPrefetchTask.java deleted file mode 100644 index 3cc179126..000000000 --- a/Branch-SDK/src/main/java/io/branch/referral/GAdsPrefetchTask.java +++ /dev/null @@ -1,136 +0,0 @@ -package io.branch.referral; - -import android.content.Context; - -import androidx.annotation.NonNull; - -import java.lang.ref.WeakReference; -import java.lang.reflect.Method; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; - -/** - *

- * Async task to fetch GAID and LAT value. - * This task fetch the GAID and LAT in background. The Background task times out after GAID_FETCH_TIME_OUT. - *

- */ -public class GAdsPrefetchTask extends BranchAsyncTask { - private static final int GAID_FETCH_TIME_OUT = 1500; - - private WeakReference contextRef_; - private final SystemObserver.AdsParamsFetchEvents callback_; - - GAdsPrefetchTask(Context context, SystemObserver.AdsParamsFetchEvents callback) { - contextRef_ = new WeakReference<>(context); - callback_ = callback; - } - - @Override - protected Void doInBackground(Void... params) { - - final CountDownLatch latch = new CountDownLatch(1); - new Thread(new Runnable() { - @Override - public void run() { - Context context = contextRef_.get(); - if (context != null) { - android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); - Object adInfoObj = getAdInfoObject(context); - - DeviceInfo di = DeviceInfo.getInstance(); - if (di == null) di = new DeviceInfo(context);// some tests complete early and garbage collect DeviceInfo singleton before this point is reached - - SystemObserver so = di.getSystemObserver(); - if (so != null) { - setGoogleLATWithAdvertisingIdClient(so, adInfoObj); - // LAT value determines whether we store GAID value or not - if (so.getLATVal() == 1) { - so.setGAID(null); - } else { - setGAIDWithAdvertisingIdClient(so, adInfoObj); - } - } - } - latch.countDown(); - } - }).start(); - - try { - //Wait GAID_FETCH_TIME_OUT milli sec max to receive the GAID and LAT - latch.await(GAID_FETCH_TIME_OUT, TimeUnit.MILLISECONDS); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - return null; - } - - @Override - protected void onPostExecute(Void aVoid) { - super.onPostExecute(aVoid); - if (callback_ != null) { - callback_.onAdsParamsFetchFinished(); - } - } - - /** - * Returns an instance of com.google.android.gms.ads.identifier.AdvertisingIdClient class to be used - * for getting GAId and LAT value - * - * @param context Context. - * @return {@link Object} instance of AdvertisingIdClient class - */ - private Object getAdInfoObject(Context context) { - Object adInfoObj = null; - if (context != null) { - try { - Class advertisingIdClientClass = Class.forName("com.google.android.gms.ads.identifier.AdvertisingIdClient"); - Method getAdvertisingIdInfoMethod = advertisingIdClientClass.getMethod("getAdvertisingIdInfo", Context.class); - adInfoObj = getAdvertisingIdInfoMethod.invoke(null, context); - } catch (Exception ignore) { - PrefHelper.Debug("Either class com.google.android.gms.ads.identifier.AdvertisingIdClient " + - "or its method, getAdvertisingIdInfo, was not found"); - } - } - return adInfoObj; - } - - /** - *

Set the limit-ad-tracking status of the advertising identifier.

- *

Check the Google Play services to for LAT enabled or disabled and return the LAT value as an integer.

- * - * @param adInfoObj AdvertisingIdClient. - * @see - * Android Developers - Limit Ad Tracking - */ - private void setGoogleLATWithAdvertisingIdClient(@NonNull SystemObserver so, Object adInfoObj) { - try { - Method getLatMethod = adInfoObj.getClass().getMethod("isLimitAdTrackingEnabled"); - Object latEnabled = getLatMethod.invoke(adInfoObj); - if (latEnabled instanceof Boolean) { - so.setLAT((Boolean) latEnabled ? 1 : 0); - } - } catch (Exception ignore) { - PrefHelper.Debug("isLimitAdTrackingEnabled method not found"); - } - } - - - /** - *

Google now requires that all apps use a standardised Advertising ID for all ad-based - * actions within Android apps.

- *

The Google Play services APIs expose the advertising tracking ID as UUID such as this:

- *
38400000-8cf0-11bd-b23e-10b96e40000d
- * - * @param adInfoObj AdvertisingIdClient. - * @see Android Developers - Advertising ID - */ - private void setGAIDWithAdvertisingIdClient(@NonNull SystemObserver so, Object adInfoObj) { - try { - Method getIdMethod = adInfoObj.getClass().getMethod("getId"); - so.setGAID((String) getIdMethod.invoke(adInfoObj)); - } catch (Exception ignore) { - } - } -} \ No newline at end of file diff --git a/Branch-SDK/src/main/java/io/branch/referral/SystemObserver.java b/Branch-SDK/src/main/java/io/branch/referral/SystemObserver.java index 2801eac32..f79b1d235 100644 --- a/Branch-SDK/src/main/java/io/branch/referral/SystemObserver.java +++ b/Branch-SDK/src/main/java/io/branch/referral/SystemObserver.java @@ -34,6 +34,14 @@ import static android.content.Context.UI_MODE_SERVICE; +import com.google.android.gms.ads.identifier.AdvertisingIdClient; + +import io.branch.coroutines.DataCoroutinesKt; +import io.branch.referral.util.DependencyUtilsKt; +import kotlin.coroutines.Continuation; +import kotlin.coroutines.CoroutineContext; +import kotlin.coroutines.EmptyCoroutineContext; + /** *

Class that provides a series of methods providing access to commonly used, device-wide * attributes and parameters used by the Branch class, and made publicly available for use by @@ -446,12 +454,68 @@ boolean prefetchAdsParams(Context context, AdsParamsFetchEvents callback) { if (isHuaweiMobileServicesAvailable(context)) { new HuaweiOAIDFetchTask(context, callback).executeTask(); } else { - new GAdsPrefetchTask(context, callback).executeTask(); + this.executeAdInfoCoroutine(context, callback); } } return isPrefetchStarted; } + private void executeAdInfoCoroutine(Context context, AdsParamsFetchEvents callback) { + if(DependencyUtilsKt.classExists(DependencyUtilsKt.playStoreAdvertisingIdClientClass)) { + DataCoroutinesKt.getAdvertisingInfoObject(context, new Continuation() { + @NonNull + @Override + public CoroutineContext getContext() { + return EmptyCoroutineContext.INSTANCE; + } + + @Override + public void resumeWith(Object o) { + if (o != null) { + try { + AdvertisingIdClient.Info info = (AdvertisingIdClient.Info) o; + + DeviceInfo di = DeviceInfo.getInstance(); + if (di == null) { + di = new DeviceInfo(context); + } + + SystemObserver so = di.getSystemObserver(); + if (so != null) { + boolean latEnabled = info.isLimitAdTrackingEnabled(); + so.setLAT(latEnabled ? 1 : 0); + + // if limit ad tracking is enabled, null any advertising id + if (latEnabled) { + so.setGAID(null); + } + else { + so.setGAID(info.getId()); + } + } + } + catch (Exception e) { + PrefHelper.Debug("Error in continuation: " + e); + } + finally { + if (callback != null) { + callback.onAdsParamsFetchFinished(); + } + } + } + } + }); + } + else { + if (callback != null) { + callback.onAdsParamsFetchFinished(); + } + + PrefHelper.Debug("Play Store service not found. " + + "If not expected, import com.google.android.gms:play-services-ads-identifier into your gradle dependencies"); + } + } + private void setFireAdId(Context context, AdsParamsFetchEvents callback) { if (context != null) { try { diff --git a/Branch-SDK/src/main/java/io/branch/referral/util/DependencyUtils.kt b/Branch-SDK/src/main/java/io/branch/referral/util/DependencyUtils.kt new file mode 100644 index 000000000..11517e10b --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/util/DependencyUtils.kt @@ -0,0 +1,26 @@ +package io.branch.referral.util + +import io.branch.referral.PrefHelper + +fun classExists(className: String): Boolean { + return try { + Class.forName(className) + true + } catch (e: ClassNotFoundException) { + PrefHelper.Debug("Could not find $className. If expected, import the dependency into your app.") + false + } +} + +const val playStoreInstallReferrerClass = "com.android.installreferrer.api.InstallReferrerClient" + +const val playStoreAdvertisingIdClientClass = + "com.google.android.gms.ads.identifier.AdvertisingIdClient" + +const val huaweiInstallReferrerClass = + "com.huawei.hms.ads.installreferrer.api.InstallReferrerClient" + +const val galaxyStoreInstallReferrerClass = + "com.sec.android.app.samsungapps.installreferrer.api.InstallReferrerClient" + +const val xiaomiInstallReferrerClass = "com.miui.referrer.api.GetAppsReferrerClient" \ No newline at end of file From f9c4b959dc30cd8405fdd2c97a3f4976192dafb1 Mon Sep 17 00:00:00 2001 From: gdeluna-branch Date: Wed, 26 Jul 2023 17:58:18 -0700 Subject: [PATCH 3/4] fix:Remove Jetifier flag (#1083) --- gradle.properties | 1 - 1 file changed, 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 43e57f008..a93dcba57 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,4 +18,3 @@ ANDROID_BUILD_TARGET_SDK_VERSION=33 ANDROID_BUILD_TOOLS_VERSION=30.0.2 ANDROID_BUILD_SDK_VERSION=33 android.useAndroidX=true -android.enableJetifier=true From 79ed736630e110b5a14906b3a88ba67623d8fe8e Mon Sep 17 00:00:00 2001 From: Nipun Singh <88689850+nsingh-branch@users.noreply.github.com> Date: Thu, 27 Jul 2023 17:29:44 -0700 Subject: [PATCH 4/4] [SDK-2040] Updated Google Play Billing library to optional and v6.0.1 (#1090) * Updated import and added check * Updated class name * Updated testbed version * Added test import * Revert "fix:Convert GAID AsyncTask to coroutine (#1084)" (#1091) This reverts commit 52c82fef194743d59ac654524545e4df71866d3b. --------- Co-authored-by: gdeluna-branch --- Branch-SDK-TestBed/build.gradle.kts | 2 +- Branch-SDK/build.gradle.kts | 4 +- .../referral/BranchTestRequestUtil.java | 2 +- .../io/branch/coroutines/DataCoroutines.kt | 19 --- .../main/java/io/branch/referral/Branch.java | 22 +-- .../io/branch/referral/GAdsPrefetchTask.java | 136 ++++++++++++++++++ .../io/branch/referral/SystemObserver.java | 66 +-------- .../branch/referral/util/DependencyUtils.kt | 4 +- 8 files changed, 159 insertions(+), 96 deletions(-) delete mode 100644 Branch-SDK/src/main/java/io/branch/coroutines/DataCoroutines.kt create mode 100644 Branch-SDK/src/main/java/io/branch/referral/GAdsPrefetchTask.java diff --git a/Branch-SDK-TestBed/build.gradle.kts b/Branch-SDK-TestBed/build.gradle.kts index 107ec220b..fea57d583 100644 --- a/Branch-SDK-TestBed/build.gradle.kts +++ b/Branch-SDK-TestBed/build.gradle.kts @@ -13,7 +13,7 @@ dependencies { implementation("androidx.browser:browser:1.0.0") { exclude(module = "support-v4") } - implementation("com.android.billingclient:billing:5.1.0") + implementation("com.android.billingclient:billing:6.0.1") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test:runner:1.5.2") diff --git a/Branch-SDK/build.gradle.kts b/Branch-SDK/build.gradle.kts index 3c09868bb..59708c972 100644 --- a/Branch-SDK/build.gradle.kts +++ b/Branch-SDK/build.gradle.kts @@ -15,7 +15,6 @@ dependencies { implementation("androidx.annotation:annotation:1.4.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.4") implementation("com.android.installreferrer:installreferrer:2.2") - implementation("com.android.billingclient:billing:5.1.0") // --- optional dependencies ----- //Please note that the Branch SDK does not require any of the below optional dependencies to operate. This dependency is listed here so there will not be build errors, @@ -23,6 +22,7 @@ dependencies { compileOnly("com.google.android.gms:play-services-ads-identifier:18.0.1") compileOnly("com.huawei.hms:ads-installreferrer:3.4.39.302") + compileOnly("com.android.billingclient:billing:6.0.1") androidTestImplementation("androidx.test.ext:junit:1.1.5") androidTestImplementation("androidx.test:runner:1.5.2") @@ -30,6 +30,8 @@ dependencies { androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1") androidTestImplementation("org.skyscreamer:jsonassert:1.5.0") androidTestImplementation("com.google.android.gms:play-services-ads-identifier:18.0.1") + androidTestImplementation("com.android.billingclient:billing:6.0.1") + testImplementation("junit:junit:4.13.2") testImplementation("org.json:json:20230227") diff --git a/Branch-SDK/src/androidTest/java/io/branch/referral/BranchTestRequestUtil.java b/Branch-SDK/src/androidTest/java/io/branch/referral/BranchTestRequestUtil.java index da33ee57c..3c0dca7a7 100644 --- a/Branch-SDK/src/androidTest/java/io/branch/referral/BranchTestRequestUtil.java +++ b/Branch-SDK/src/androidTest/java/io/branch/referral/BranchTestRequestUtil.java @@ -20,7 +20,7 @@ abstract class BranchTestRequestUtil { // can be pretty short because we mock remote interface and don't actually make async calls from the SDK public static final int TEST_REQUEST_TIMEOUT = 1000; - public static final int TEST_INIT_SESSION_TIMEOUT = 10000; + public static final int TEST_INIT_SESSION_TIMEOUT = 5000; // Dig out the variable for isStandardEvent from the BranchEvent object. protected boolean isStandardEvent(BranchEvent event) throws Exception { diff --git a/Branch-SDK/src/main/java/io/branch/coroutines/DataCoroutines.kt b/Branch-SDK/src/main/java/io/branch/coroutines/DataCoroutines.kt deleted file mode 100644 index cfd61f2a0..000000000 --- a/Branch-SDK/src/main/java/io/branch/coroutines/DataCoroutines.kt +++ /dev/null @@ -1,19 +0,0 @@ -package io.branch.coroutines - -import android.content.Context -import com.google.android.gms.ads.identifier.AdvertisingIdClient -import io.branch.referral.PrefHelper -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext - -suspend fun getAdvertisingInfoObject(context: Context): AdvertisingIdClient.Info? { - return withContext(Dispatchers.Default) { - try { - AdvertisingIdClient.getAdvertisingIdInfo(context) - } - catch (exception: Exception) { - PrefHelper.Debug("getAdvertisingIdInfo exception: $exception") - null - } - } -} \ No newline at end of file diff --git a/Branch-SDK/src/main/java/io/branch/referral/Branch.java b/Branch-SDK/src/main/java/io/branch/referral/Branch.java index e1e5d0ad5..baee1eedc 100644 --- a/Branch-SDK/src/main/java/io/branch/referral/Branch.java +++ b/Branch-SDK/src/main/java/io/branch/referral/Branch.java @@ -5,6 +5,7 @@ import static io.branch.referral.BranchPreinstall.getPreinstallSystemData; import static io.branch.referral.BranchUtil.isTestModeEnabled; import static io.branch.referral.PrefHelper.isValidBranchKey; +import static io.branch.referral.util.DependencyUtilsKt.billingGooglePlayClass; import static io.branch.referral.util.DependencyUtilsKt.classExists; import static io.branch.referral.util.DependencyUtilsKt.galaxyStoreInstallReferrerClass; import static io.branch.referral.util.DependencyUtilsKt.huaweiInstallReferrerClass; @@ -428,6 +429,9 @@ private Branch(@NonNull Context context) { branchPluginSupport_ = new BranchPluginSupport(context); branchQRCodeCache_ = new BranchQRCodeCache(context); requestQueue_ = ServerRequestQueue.getInstance(context); + if (!trackingController.isTrackingDisabled()) { // Do not get GAID when tracking is disabled + isGAParamsFetchInProgress_ = deviceInfo_.getSystemObserver().prefetchAdsParams(context,this); + } } /** @@ -3266,13 +3270,15 @@ public static void notifyNativeToInit(){ } public void logEventWithPurchase(@NonNull Context context, @NonNull Purchase purchase) { - BillingGooglePlay.Companion.getInstance().startBillingClient(succeeded -> { - if (succeeded) { - BillingGooglePlay.Companion.getInstance().logEventWithPurchase(context, purchase); - } else { - PrefHelper.LogException("Cannot log IAP event. Billing client setup failed", new Exception("Billing Client Setup Failed")); - } - return null; - }); + if (classExists(billingGooglePlayClass)) { + BillingGooglePlay.Companion.getInstance().startBillingClient(succeeded -> { + if (succeeded) { + BillingGooglePlay.Companion.getInstance().logEventWithPurchase(context, purchase); + } else { + PrefHelper.LogException("Cannot log IAP event. Billing client setup failed", new Exception("Billing Client Setup Failed")); + } + return null; + }); + } } } diff --git a/Branch-SDK/src/main/java/io/branch/referral/GAdsPrefetchTask.java b/Branch-SDK/src/main/java/io/branch/referral/GAdsPrefetchTask.java new file mode 100644 index 000000000..3cc179126 --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/GAdsPrefetchTask.java @@ -0,0 +1,136 @@ +package io.branch.referral; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import java.lang.ref.WeakReference; +import java.lang.reflect.Method; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +/** + *

+ * Async task to fetch GAID and LAT value. + * This task fetch the GAID and LAT in background. The Background task times out after GAID_FETCH_TIME_OUT. + *

+ */ +public class GAdsPrefetchTask extends BranchAsyncTask { + private static final int GAID_FETCH_TIME_OUT = 1500; + + private WeakReference contextRef_; + private final SystemObserver.AdsParamsFetchEvents callback_; + + GAdsPrefetchTask(Context context, SystemObserver.AdsParamsFetchEvents callback) { + contextRef_ = new WeakReference<>(context); + callback_ = callback; + } + + @Override + protected Void doInBackground(Void... params) { + + final CountDownLatch latch = new CountDownLatch(1); + new Thread(new Runnable() { + @Override + public void run() { + Context context = contextRef_.get(); + if (context != null) { + android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_URGENT_AUDIO); + Object adInfoObj = getAdInfoObject(context); + + DeviceInfo di = DeviceInfo.getInstance(); + if (di == null) di = new DeviceInfo(context);// some tests complete early and garbage collect DeviceInfo singleton before this point is reached + + SystemObserver so = di.getSystemObserver(); + if (so != null) { + setGoogleLATWithAdvertisingIdClient(so, adInfoObj); + // LAT value determines whether we store GAID value or not + if (so.getLATVal() == 1) { + so.setGAID(null); + } else { + setGAIDWithAdvertisingIdClient(so, adInfoObj); + } + } + } + latch.countDown(); + } + }).start(); + + try { + //Wait GAID_FETCH_TIME_OUT milli sec max to receive the GAID and LAT + latch.await(GAID_FETCH_TIME_OUT, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + super.onPostExecute(aVoid); + if (callback_ != null) { + callback_.onAdsParamsFetchFinished(); + } + } + + /** + * Returns an instance of com.google.android.gms.ads.identifier.AdvertisingIdClient class to be used + * for getting GAId and LAT value + * + * @param context Context. + * @return {@link Object} instance of AdvertisingIdClient class + */ + private Object getAdInfoObject(Context context) { + Object adInfoObj = null; + if (context != null) { + try { + Class advertisingIdClientClass = Class.forName("com.google.android.gms.ads.identifier.AdvertisingIdClient"); + Method getAdvertisingIdInfoMethod = advertisingIdClientClass.getMethod("getAdvertisingIdInfo", Context.class); + adInfoObj = getAdvertisingIdInfoMethod.invoke(null, context); + } catch (Exception ignore) { + PrefHelper.Debug("Either class com.google.android.gms.ads.identifier.AdvertisingIdClient " + + "or its method, getAdvertisingIdInfo, was not found"); + } + } + return adInfoObj; + } + + /** + *

Set the limit-ad-tracking status of the advertising identifier.

+ *

Check the Google Play services to for LAT enabled or disabled and return the LAT value as an integer.

+ * + * @param adInfoObj AdvertisingIdClient. + * @see + * Android Developers - Limit Ad Tracking + */ + private void setGoogleLATWithAdvertisingIdClient(@NonNull SystemObserver so, Object adInfoObj) { + try { + Method getLatMethod = adInfoObj.getClass().getMethod("isLimitAdTrackingEnabled"); + Object latEnabled = getLatMethod.invoke(adInfoObj); + if (latEnabled instanceof Boolean) { + so.setLAT((Boolean) latEnabled ? 1 : 0); + } + } catch (Exception ignore) { + PrefHelper.Debug("isLimitAdTrackingEnabled method not found"); + } + } + + + /** + *

Google now requires that all apps use a standardised Advertising ID for all ad-based + * actions within Android apps.

+ *

The Google Play services APIs expose the advertising tracking ID as UUID such as this:

+ *
38400000-8cf0-11bd-b23e-10b96e40000d
+ * + * @param adInfoObj AdvertisingIdClient. + * @see Android Developers - Advertising ID + */ + private void setGAIDWithAdvertisingIdClient(@NonNull SystemObserver so, Object adInfoObj) { + try { + Method getIdMethod = adInfoObj.getClass().getMethod("getId"); + so.setGAID((String) getIdMethod.invoke(adInfoObj)); + } catch (Exception ignore) { + } + } +} \ No newline at end of file diff --git a/Branch-SDK/src/main/java/io/branch/referral/SystemObserver.java b/Branch-SDK/src/main/java/io/branch/referral/SystemObserver.java index f79b1d235..2801eac32 100644 --- a/Branch-SDK/src/main/java/io/branch/referral/SystemObserver.java +++ b/Branch-SDK/src/main/java/io/branch/referral/SystemObserver.java @@ -34,14 +34,6 @@ import static android.content.Context.UI_MODE_SERVICE; -import com.google.android.gms.ads.identifier.AdvertisingIdClient; - -import io.branch.coroutines.DataCoroutinesKt; -import io.branch.referral.util.DependencyUtilsKt; -import kotlin.coroutines.Continuation; -import kotlin.coroutines.CoroutineContext; -import kotlin.coroutines.EmptyCoroutineContext; - /** *

Class that provides a series of methods providing access to commonly used, device-wide * attributes and parameters used by the Branch class, and made publicly available for use by @@ -454,68 +446,12 @@ boolean prefetchAdsParams(Context context, AdsParamsFetchEvents callback) { if (isHuaweiMobileServicesAvailable(context)) { new HuaweiOAIDFetchTask(context, callback).executeTask(); } else { - this.executeAdInfoCoroutine(context, callback); + new GAdsPrefetchTask(context, callback).executeTask(); } } return isPrefetchStarted; } - private void executeAdInfoCoroutine(Context context, AdsParamsFetchEvents callback) { - if(DependencyUtilsKt.classExists(DependencyUtilsKt.playStoreAdvertisingIdClientClass)) { - DataCoroutinesKt.getAdvertisingInfoObject(context, new Continuation() { - @NonNull - @Override - public CoroutineContext getContext() { - return EmptyCoroutineContext.INSTANCE; - } - - @Override - public void resumeWith(Object o) { - if (o != null) { - try { - AdvertisingIdClient.Info info = (AdvertisingIdClient.Info) o; - - DeviceInfo di = DeviceInfo.getInstance(); - if (di == null) { - di = new DeviceInfo(context); - } - - SystemObserver so = di.getSystemObserver(); - if (so != null) { - boolean latEnabled = info.isLimitAdTrackingEnabled(); - so.setLAT(latEnabled ? 1 : 0); - - // if limit ad tracking is enabled, null any advertising id - if (latEnabled) { - so.setGAID(null); - } - else { - so.setGAID(info.getId()); - } - } - } - catch (Exception e) { - PrefHelper.Debug("Error in continuation: " + e); - } - finally { - if (callback != null) { - callback.onAdsParamsFetchFinished(); - } - } - } - } - }); - } - else { - if (callback != null) { - callback.onAdsParamsFetchFinished(); - } - - PrefHelper.Debug("Play Store service not found. " + - "If not expected, import com.google.android.gms:play-services-ads-identifier into your gradle dependencies"); - } - } - private void setFireAdId(Context context, AdsParamsFetchEvents callback) { if (context != null) { try { diff --git a/Branch-SDK/src/main/java/io/branch/referral/util/DependencyUtils.kt b/Branch-SDK/src/main/java/io/branch/referral/util/DependencyUtils.kt index 11517e10b..d11205dd8 100644 --- a/Branch-SDK/src/main/java/io/branch/referral/util/DependencyUtils.kt +++ b/Branch-SDK/src/main/java/io/branch/referral/util/DependencyUtils.kt @@ -23,4 +23,6 @@ const val huaweiInstallReferrerClass = const val galaxyStoreInstallReferrerClass = "com.sec.android.app.samsungapps.installreferrer.api.InstallReferrerClient" -const val xiaomiInstallReferrerClass = "com.miui.referrer.api.GetAppsReferrerClient" \ No newline at end of file +const val xiaomiInstallReferrerClass = "com.miui.referrer.api.GetAppsReferrerClient" + +const val billingGooglePlayClass = "com.android.billingclient.api.BillingClient" \ No newline at end of file