From fe16b68ed9042cb892071cd02f8a933d4ce5eaa0 Mon Sep 17 00:00:00 2001 From: rob-gioia-branch Date: Fri, 27 Sep 2024 16:20:02 -0400 Subject: [PATCH] Added a popup dialog displaying the Integration Validator results with options to view more details and export the logs Added a popup dialog displaying the Integration Validator results with options to view more details and export the logs --- Branch-SDK/build.gradle.kts | 1 + Branch-SDK/src/main/AndroidManifest.xml | 5 + .../validators/AlternateDomainsCheck.java | 47 +++ .../referral/validators/AppLinksCheck.java | 48 +++ .../BranchInstanceCreationValidatorCheck.java | 34 ++ .../validators/BranchKeysValidatorCheck.java | 30 ++ .../validators/CustomDomainCheck.java | 48 +++ .../validators/DefaultDomainsCheck.java | 48 +++ .../validators/IntegrationValidator.java | 291 ++++++------------ .../validators/IntegrationValidatorCheck.java | 24 ++ .../IntegrationValidatorConstants.java | 6 + .../IntegrationValidatorDialog.java | 109 +++++++ .../IntegrationValidatorDialogRowItem.java | 77 +++++ .../referral/validators/PackageNameCheck.java | 35 +++ .../referral/validators/URISchemeCheck.java | 91 ++++++ .../src/main/res/drawable/branch_icon.png | Bin 0 -> 6774 bytes .../layout/dialog_integration_validator.xml | 146 +++++++++ .../integration_validator_dialog_row_item.xml | 51 +++ 18 files changed, 896 insertions(+), 195 deletions(-) create mode 100644 Branch-SDK/src/main/java/io/branch/referral/validators/AlternateDomainsCheck.java create mode 100644 Branch-SDK/src/main/java/io/branch/referral/validators/AppLinksCheck.java create mode 100644 Branch-SDK/src/main/java/io/branch/referral/validators/BranchInstanceCreationValidatorCheck.java create mode 100644 Branch-SDK/src/main/java/io/branch/referral/validators/BranchKeysValidatorCheck.java create mode 100644 Branch-SDK/src/main/java/io/branch/referral/validators/CustomDomainCheck.java create mode 100644 Branch-SDK/src/main/java/io/branch/referral/validators/DefaultDomainsCheck.java create mode 100644 Branch-SDK/src/main/java/io/branch/referral/validators/IntegrationValidatorCheck.java create mode 100644 Branch-SDK/src/main/java/io/branch/referral/validators/IntegrationValidatorConstants.java create mode 100644 Branch-SDK/src/main/java/io/branch/referral/validators/IntegrationValidatorDialog.java create mode 100644 Branch-SDK/src/main/java/io/branch/referral/validators/IntegrationValidatorDialogRowItem.java create mode 100644 Branch-SDK/src/main/java/io/branch/referral/validators/PackageNameCheck.java create mode 100644 Branch-SDK/src/main/java/io/branch/referral/validators/URISchemeCheck.java create mode 100644 Branch-SDK/src/main/res/drawable/branch_icon.png create mode 100644 Branch-SDK/src/main/res/layout/dialog_integration_validator.xml create mode 100644 Branch-SDK/src/main/res/layout/integration_validator_dialog_row_item.xml diff --git a/Branch-SDK/build.gradle.kts b/Branch-SDK/build.gradle.kts index 1040ccbb8..5d03b308f 100644 --- a/Branch-SDK/build.gradle.kts +++ b/Branch-SDK/build.gradle.kts @@ -19,6 +19,7 @@ dependencies { implementation(kotlin("stdlib-jdk8")) implementation("androidx.annotation:annotation:1.4.0") implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion") + implementation("androidx.constraintlayout:constraintlayout:2.1.4") // --- optional dependencies ----- // Please note that the Branch SDK does not require any of the below optional dependencies to operate. diff --git a/Branch-SDK/src/main/AndroidManifest.xml b/Branch-SDK/src/main/AndroidManifest.xml index 20a5e112d..11f8703fd 100644 --- a/Branch-SDK/src/main/AndroidManifest.xml +++ b/Branch-SDK/src/main/AndroidManifest.xml @@ -1,5 +1,10 @@ + + + diff --git a/Branch-SDK/src/main/java/io/branch/referral/validators/AlternateDomainsCheck.java b/Branch-SDK/src/main/java/io/branch/referral/validators/AlternateDomainsCheck.java new file mode 100644 index 000000000..040137dc4 --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/AlternateDomainsCheck.java @@ -0,0 +1,47 @@ +package io.branch.referral.validators; + +import android.content.Context; +import android.text.TextUtils; + +import org.json.JSONObject; + +public class AlternateDomainsCheck extends IntegrationValidatorCheck { + String name = "Alt Domains"; + String errorMessage = "Could not find intent filter to support alternate link domain. Please add intent filter for handling alternate link domain in your Android Manifest file"; + String moreInfoLink = "More info"; + BranchIntegrationModel integrationModel; + JSONObject branchAppConfig; + + public AlternateDomainsCheck(BranchIntegrationModel integrationModel, JSONObject branchAppConfig) { + super.name = name; + super.errorMessage = errorMessage; + super.moreInfoLink = moreInfoLink; + this.integrationModel = integrationModel; + this.branchAppConfig = branchAppConfig; + } + + @Override + public boolean RunTests(Context context) { + String alternateAppLinkDomain = branchAppConfig.optString("alternate_short_url_domain"); + return TextUtils.isEmpty(alternateAppLinkDomain) || checkIfIntentAddedForLinkDomain(alternateAppLinkDomain); + } + + @Override + public String GetOutput(Context context, boolean didTestSucceed) { + didTestSucceed = RunTests(context); + return super.GetOutput(context, didTestSucceed); + } + + private boolean checkIfIntentAddedForLinkDomain(String domainName) { + boolean foundIntentFilterMatchingDomainName = false; + if (!TextUtils.isEmpty(domainName)) { + for (String host : integrationModel.applinkScheme) { + if (domainName.equals(host)) { + foundIntentFilterMatchingDomainName = true; + break; + } + } + } + return foundIntentFilterMatchingDomainName; + } +} diff --git a/Branch-SDK/src/main/java/io/branch/referral/validators/AppLinksCheck.java b/Branch-SDK/src/main/java/io/branch/referral/validators/AppLinksCheck.java new file mode 100644 index 000000000..ba21aa6fe --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/AppLinksCheck.java @@ -0,0 +1,48 @@ +package io.branch.referral.validators; + +import android.content.Context; +import android.text.TextUtils; + +import org.json.JSONObject; + +public class AppLinksCheck extends IntegrationValidatorCheck { + + String name = "App Links"; + String errorMessage = "Could not find any App Link hosts to support Android AppLinks. Please add intent filter for handling AppLinks in your Android Manifest file"; + String moreInfoLink = "More info"; + BranchIntegrationModel integrationModel; + JSONObject branchAppConfig; + + public AppLinksCheck(BranchIntegrationModel integrationModel, JSONObject branchAppConfig) { + super.name = name; + super.errorMessage = errorMessage; + super.moreInfoLink = moreInfoLink; + this.integrationModel = integrationModel; + this.branchAppConfig = branchAppConfig; + } + + @Override + public boolean RunTests(Context context) { + String defAppLinkDomain = branchAppConfig.optString("default_short_url_domain"); + return !integrationModel.applinkScheme.isEmpty() && !TextUtils.isEmpty(defAppLinkDomain) && checkIfIntentAddedForLinkDomain(defAppLinkDomain); + } + + @Override + public String GetOutput(Context context, boolean didTestSucceed) { + didTestSucceed = RunTests(context); + return super.GetOutput(context, didTestSucceed); + } + + private boolean checkIfIntentAddedForLinkDomain(String domainName) { + boolean foundIntentFilterMatchingDomainName = false; + if (!TextUtils.isEmpty(domainName) && integrationModel.applinkScheme != null) { + for (String host : integrationModel.applinkScheme) { + if (domainName.equals(host)) { + foundIntentFilterMatchingDomainName = true; + break; + } + } + } + return foundIntentFilterMatchingDomainName; + } +} diff --git a/Branch-SDK/src/main/java/io/branch/referral/validators/BranchInstanceCreationValidatorCheck.java b/Branch-SDK/src/main/java/io/branch/referral/validators/BranchInstanceCreationValidatorCheck.java new file mode 100644 index 000000000..4c3e5067c --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/BranchInstanceCreationValidatorCheck.java @@ -0,0 +1,34 @@ +package io.branch.referral.validators; + +import android.content.Context; + +import io.branch.referral.Branch; + +public class BranchInstanceCreationValidatorCheck extends IntegrationValidatorCheck { + + String name = "Branch instance"; + String errorMessage = "Branch is not initialised from your Application class. Please add `Branch.getAutoInstance(this);` to your Application#onCreate() method."; + String moreInfoLink = "More info"; + + public BranchInstanceCreationValidatorCheck() { + super.name = name; + super.errorMessage = errorMessage; + super.moreInfoLink = moreInfoLink; + } + + @Override + public boolean RunTests(Context context) { + return Branch.getInstance() != null; + } + + @Override + public String GetOutput(Context context, boolean didTestSucceed) { + didTestSucceed = RunTests(context); + return super.GetOutput(context, didTestSucceed); + } + + @Override + public String GetMoreInfoLink() { + return moreInfoLink; + } +} diff --git a/Branch-SDK/src/main/java/io/branch/referral/validators/BranchKeysValidatorCheck.java b/Branch-SDK/src/main/java/io/branch/referral/validators/BranchKeysValidatorCheck.java new file mode 100644 index 000000000..c94a15fc1 --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/BranchKeysValidatorCheck.java @@ -0,0 +1,30 @@ +package io.branch.referral.validators; + +import android.content.Context; +import android.text.TextUtils; + +import io.branch.referral.BranchUtil; + +public class BranchKeysValidatorCheck extends IntegrationValidatorCheck { + + String name = "Branch Keys"; + String errorMessage = "Unable to read Branch keys from your application. Did you forget to add Branch keys in your application?."; + String moreInfoLink = "More info"; + + public BranchKeysValidatorCheck() { + super.name = name; + super.errorMessage = errorMessage; + super.moreInfoLink = moreInfoLink; + } + + @Override + public boolean RunTests(Context context) { + return !TextUtils.isEmpty(BranchUtil.readBranchKey(context)); + } + + @Override + public String GetOutput(Context context, boolean didTestSucceed) { + didTestSucceed = RunTests(context); + return super.GetOutput(context, didTestSucceed); + } +} diff --git a/Branch-SDK/src/main/java/io/branch/referral/validators/CustomDomainCheck.java b/Branch-SDK/src/main/java/io/branch/referral/validators/CustomDomainCheck.java new file mode 100644 index 000000000..5b8f0b763 --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/CustomDomainCheck.java @@ -0,0 +1,48 @@ +package io.branch.referral.validators; + +import android.content.Context; +import android.text.TextUtils; + +import org.json.JSONObject; + +public class CustomDomainCheck extends IntegrationValidatorCheck { + + String name = "Custom Domain"; + String errorMessage = "Could not find intent filter to support Branch default link domain. Please add intent filter for handling custom link domain in your Android Manifest file"; + String moreInfoLink = "More info"; + BranchIntegrationModel integrationModel; + JSONObject branchAppConfig; + + public CustomDomainCheck(BranchIntegrationModel integrationModel, JSONObject branchAppConfig) { + super.name = name; + super.errorMessage = errorMessage; + super.moreInfoLink = moreInfoLink; + this.integrationModel = integrationModel; + this.branchAppConfig = branchAppConfig; + } + + @Override + public boolean RunTests(Context context) { + String customDomain = branchAppConfig.optString("short_url_domain"); + return TextUtils.isEmpty(customDomain) || checkIfIntentAddedForLinkDomain(customDomain); + } + + @Override + public String GetOutput(Context context, boolean didTestSucceed) { + didTestSucceed = RunTests(context); + return super.GetOutput(context, didTestSucceed); + } + + private boolean checkIfIntentAddedForLinkDomain(String domainName) { + boolean foundIntentFilterMatchingDomainName = false; + if (!TextUtils.isEmpty(domainName)) { + for (String host : integrationModel.applinkScheme) { + if (domainName.equals(host)) { + foundIntentFilterMatchingDomainName = true; + break; + } + } + } + return foundIntentFilterMatchingDomainName; + } +} diff --git a/Branch-SDK/src/main/java/io/branch/referral/validators/DefaultDomainsCheck.java b/Branch-SDK/src/main/java/io/branch/referral/validators/DefaultDomainsCheck.java new file mode 100644 index 000000000..e1e3c18e9 --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/DefaultDomainsCheck.java @@ -0,0 +1,48 @@ +package io.branch.referral.validators; + +import android.content.Context; +import android.text.TextUtils; + +import org.json.JSONObject; + +public class DefaultDomainsCheck extends IntegrationValidatorCheck { + + String name = "Default Domains"; + String errorMessage = "Could not find any App Link hosts to support Android AppLinks. Please add intent filter for handling AppLinks in your Android Manifest file"; + String moreInfoLink = "More info"; + BranchIntegrationModel integrationModel; + JSONObject branchAppConfig; + + public DefaultDomainsCheck(BranchIntegrationModel integrationModel, JSONObject branchAppConfig) { + super.name = name; + super.errorMessage = errorMessage; + super.moreInfoLink = moreInfoLink; + this.integrationModel = integrationModel; + this.branchAppConfig = branchAppConfig; + } + + @Override + public boolean RunTests(Context context) { + String defAppLinkDomain = branchAppConfig.optString("default_short_url_domain"); + return TextUtils.isEmpty(defAppLinkDomain) || checkIfIntentAddedForLinkDomain(defAppLinkDomain); + } + + @Override + public String GetOutput(Context context, boolean didTestSucceed) { + didTestSucceed = RunTests(context); + return super.GetOutput(context, didTestSucceed); + } + + private boolean checkIfIntentAddedForLinkDomain(String domainName) { + boolean foundIntentFilterMatchingDomainName = false; + if (!TextUtils.isEmpty(domainName)) { + for (String host : integrationModel.applinkScheme) { + if (domainName.equals(host)) { + foundIntentFilterMatchingDomainName = true; + break; + } + } + } + return foundIntentFilterMatchingDomainName; + } +} \ No newline at end of file diff --git a/Branch-SDK/src/main/java/io/branch/referral/validators/IntegrationValidator.java b/Branch-SDK/src/main/java/io/branch/referral/validators/IntegrationValidator.java index c5e7a12bc..a2915cd47 100644 --- a/Branch-SDK/src/main/java/io/branch/referral/validators/IntegrationValidator.java +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/IntegrationValidator.java @@ -1,21 +1,14 @@ package io.branch.referral.validators; import android.content.Context; -import android.net.Uri; -import android.text.TextUtils; import android.util.Log; +import android.view.WindowManager; -import org.json.JSONArray; import org.json.JSONObject; -import java.util.Iterator; +import java.util.Objects; import io.branch.referral.Branch; -import io.branch.referral.BranchUtil; - -/** - * Created by sojanpr on 9/15/17. - */ public class IntegrationValidator implements ServerRequestGetAppConfig.IGetAppConfigEvents { @@ -23,8 +16,16 @@ public class IntegrationValidator implements ServerRequestGetAppConfig.IGetAppCo private final BranchIntegrationModel integrationModel; private final String TAG = "BranchSDK_Doctor"; + Context context; + + boolean hasRan = false; + boolean hasTestFailed = false; + + IntegrationValidatorDialog integrationValidatorDialog; + private IntegrationValidator(Context context) { this.integrationModel = new BranchIntegrationModel(context); + this.context = context; } public static void validate(Context context) { @@ -32,207 +33,84 @@ public static void validate(Context context) { instance = new IntegrationValidator(context); } instance.validateSDKIntegration(context); + instance.integrationValidatorDialog = new IntegrationValidatorDialog(context); } private void validateSDKIntegration(Context context) { - logValidationProgress("\n\n------------------- Initiating Branch integration verification ---------------------------"); - // 1. Verify Branch Auto instance - logValidationProgress("1. Verifying Branch instance creation"); - if (Branch.getInstance() == null) { - logIntegrationError("Branch is not initialised from your Application class. Please add `Branch.getAutoInstance(this);` to your Application#onCreate() method.", - "https://help.branch.io/developers-hub/docs/android-basic-integration#section-load-branch"); - return; - } - logValidationPassed(); - - // 2. Verify Branch Keys - logValidationProgress("2. Checking Branch keys"); - if (TextUtils.isEmpty(BranchUtil.readBranchKey(context))) { - logIntegrationError("Unable to read Branch keys from your application. Did you forget to add Branch keys in your application?.", - "https://help.branch.io/developers-hub/docs/android-basic-integration#section-configure-app"); - return; - } - logValidationPassed(); - - Branch.getInstance().requestQueue_.handleNewRequest(new ServerRequestGetAppConfig(context, this)); - + Branch.getInstance().requestQueue_.handleNewRequest(new ServerRequestGetAppConfig(context, IntegrationValidator.this)); } private void doValidateWithAppConfig(JSONObject branchAppConfig) { - // 3. Verify the package name of app with Branch dash board settings - logValidationProgress("3. Verifying application package name"); - if (!integrationModel.packageName.equals(branchAppConfig.optString("android_package_name"))) { - logIntegrationError("Incorrect package name in Branch dashboard. Please correct your package name in dashboard -> Configuration page.", - "https://help.branch.io/developers-hub/docs/android-basic-integration#section-configure-branch-dashboard"); - return; - } else { - logValidationPassed(); - } - - // 4. Verify the URI scheme filters are added on the app - logValidationProgress("4. Checking Android Manifest for URI based deep link config"); - if (integrationModel.deeplinkUriScheme == null || integrationModel.deeplinkUriScheme.length() == 0) { - if (!integrationModel.appSettingsAvailable) { - logValidationProgress("- Skipping. Unable to verify the deep link config. Failed to read the Android Manifest"); - } else { - logIntegrationError(String.format("No intent found for opening the app through uri Scheme '%s'." + - "Please add the intent with URI scheme to your Android manifest.", branchAppConfig.optString("android_uri_scheme")), - "https://help.branch.io/developers-hub/docs/android-basic-integration#section-configure-app"); - return; - } - } else { - logValidationPassed(); - } - - // 5. Check if URI Scheme is added in the Branch dashboard - logValidationProgress("5. Verifying URI based deep link config with Branch dash board."); - String branchAppUriScheme = branchAppConfig.optString("android_uri_scheme"); - if (TextUtils.isEmpty(branchAppUriScheme)) { - logIntegrationError("Uri Scheme to open your app is not specified in Branch dashboard. Please add URI scheme in Branch dashboard.", - "https://help.branch.io/developers-hub/docs/android-basic-integration#section-configure-branch-dashboard"); - return; - } - logValidationPassed(); - - // 6. Check if URI Scheme matches with the Branch app settings - logValidationProgress("6. Verifying intent for receiving URI scheme."); - if (!checkIfIntentAddedForURIScheme(branchAppUriScheme)) { - if (!integrationModel.appSettingsAvailable) { - logValidationProgress("- Skipping. Unable to verify intent for receiving URI scheme. Failed to read the Android Manifest"); - } else { - logIntegrationError(String.format("Uri scheme '%s' specified in Branch dashboard doesn't match with the deep link intent in manifest file", branchAppUriScheme), - "https://help.branch.io/developers-hub/docs/android-basic-integration#section-configure-branch-dashboard"); - return; - } - } else { - logValidationPassed(); - } - - // 7. Check if AppLinks are specified in the Manifest - logValidationProgress("7. Checking AndroidManifest for AppLink config."); - if (integrationModel.applinkScheme.isEmpty()) { - if (!integrationModel.appSettingsAvailable) { - logValidationProgress("- Skipping. Unable to verify intent for receiving URI scheme. Failed to read the Android Manifest"); - } else { - logIntegrationError("Could not find any App Link hosts to support Android AppLinks. Please add intent filter for handling AppLinks in your Android Manifest file", - "https://help.branch.io/using-branch/docs/android-app-links#section-add-intent-filter-to-manifest"); - return; - } - } else { - logValidationPassed(); - } - - // 8. Look for any custom domains specified in the dash board and has matching intent filter - { - logValidationProgress("8. Verifying any supported custom link domains."); - String customDomain = branchAppConfig.optString("short_url_domain"); - if (!TextUtils.isEmpty(customDomain) && !checkIfIntentAddedForLinkDomain(customDomain)) { - if (!integrationModel.appSettingsAvailable) { - logValidationProgress("- Skipping. Unable to verify supported custom link domains. Failed to read the Android Manifest"); - } else { - logIntegrationError(String.format("Could not find intent filter to support custom link domain '%s'. Please add intent filter for handling custom link domain in your Android Manifest file ", customDomain), - "https://help.branch.io/developers-hub/docs/android-basic-integration#section-configure-app"); - return; - } - - } else { - logValidationPassed(); - } - } - - - // 9. Check for matching intent filter for default app link domains - { - logValidationProgress("9. Verifying default link domains integrations."); - String defAppLinkDomain = branchAppConfig.optString("default_short_url_domain"); - if (!TextUtils.isEmpty(defAppLinkDomain) && !checkIfIntentAddedForLinkDomain(defAppLinkDomain)) { - if (!integrationModel.appSettingsAvailable) { - logValidationProgress("- Skipping. Unable to verify default link domains. Failed to read the Android Manifest"); - } else { - logIntegrationError(String.format("Could not find intent filter to support Branch default link domain '%s'. Please add intent filter for handling custom link domain in your Android Manifest file ", defAppLinkDomain), - "https://help.branch.io/developers-hub/docs/android-basic-integration#section-configure-app"); - return; - } - } else { - logValidationPassed(); - } - } + //retrieve the Branch dashboard configurations from the server + Branch.getInstance().requestQueue_.handleNewRequest(new ServerRequestGetAppConfig(context, this)); + logValidationProgress("\n\n------------------- Initiating Branch integration verification ---------------------------"); - // 10. Check for matching intent filter for alternative app link domains - { - logValidationProgress("10. Verifying alternate link domains integrations."); - String alternateAppLinkDomain = branchAppConfig.optString("alternate_short_url_domain"); - if (!TextUtils.isEmpty(alternateAppLinkDomain) && !checkIfIntentAddedForLinkDomain(alternateAppLinkDomain)) { - if (!integrationModel.appSettingsAvailable) { - logValidationProgress("- Skipping.Unable to verify alternate link domains. Failed to read the Android Manifest"); - } else { - logIntegrationError(String.format("Could not find intent filter to support alternate link domain '%s'. Please add intent filter for handling custom link domain in your Android Manifest file ", alternateAppLinkDomain), - "https://help.branch.io/developers-hub/docs/android-basic-integration#section-configure-app"); - return; - } - } else { - logValidationPassed(); - } - } - logValidationPassed(); - Log.d(TAG, "--------------------------------------------\nSuccessfully completed Branch integration validation. Everything looks good!"); - Log.d(TAG, "\n Great! Comment out the 'validateSDKIntegration' line in your app. Next check your deep link routing.\n" + - " Append '?bnc_validate=true' to any of your app's Branch links and click it on your mobile device (not the Simulator!) to start the test.\n" + - " For instance, to validate a link like:\n" + - " https://.app.link/NdJ6nFzRbK\n" + - " click on:\n" + - " https://.app.link/NdJ6nFzRbK?bnc_validate=true"); - } + // 1. Verify Branch Auto instance + BranchInstanceCreationValidatorCheck branchInstanceCreationValidatorCheck = new BranchInstanceCreationValidatorCheck(); + boolean result = branchInstanceCreationValidatorCheck.RunTests(context); + integrationValidatorDialog.setTestResult(1, branchInstanceCreationValidatorCheck.GetTestName(), result, branchInstanceCreationValidatorCheck.GetOutput(context, result), branchInstanceCreationValidatorCheck.GetMoreInfoLink()); + logOutputForTest(result, "1. Verifying Branch instance creation", "Branch is not initialised from your Application class. Please add `Branch.getAutoInstance(this);` to your Application#onCreate() method.", "https://help.branch.io/developers-hub/docs/android-basic-integration#section-load-branch"); - private boolean checkIfIntentAddedForURIScheme(String uriScheme) { - Uri branchDeepLinkURI = Uri.parse(uriScheme); - String uriHost = branchDeepLinkURI.getScheme(); - String uriPath = branchDeepLinkURI.getHost(); - uriPath = TextUtils.isEmpty(uriPath) ? "open" : uriPath; - boolean foundMatchingUri = false; - if (integrationModel.deeplinkUriScheme != null) { - for (Iterator it = integrationModel.deeplinkUriScheme.keys(); it.hasNext(); ) { - String key = it.next(); - if (uriHost != null && uriHost.equals(key)) { - JSONArray hosts = integrationModel.deeplinkUriScheme.optJSONArray(key); - if (hosts != null && hosts.length() > 0) { - for (int i = 0; i < hosts.length(); ++i) { - if (uriPath != null && uriPath.equals(hosts.optString(i))) { - foundMatchingUri = true; - break; - } - } - } else { - foundMatchingUri = true; - break; - } - } - } - } - return foundMatchingUri; - } + // 2. Verify Branch Keys + BranchKeysValidatorCheck branchKeysValidatorCheck = new BranchKeysValidatorCheck(); + result = branchKeysValidatorCheck.RunTests(context); + integrationValidatorDialog.setTestResult(2, branchKeysValidatorCheck.GetTestName(), result, branchKeysValidatorCheck.GetOutput(context, result), branchKeysValidatorCheck.GetMoreInfoLink()); + logOutputForTest(result, "2. Checking Branch keys", "Unable to read Branch keys from your application. Did you forget to add Branch keys in your application?.", "https://help.branch.io/developers-hub/docs/android-basic-integration#section-configure-app"); - private boolean checkIfIntentAddedForLinkDomain(String domainName) { - boolean foundIntentFilterMatchingDomainName = false; - if (!TextUtils.isEmpty(domainName) && integrationModel.applinkScheme != null) { - for (String host : integrationModel.applinkScheme) { - if (domainName.equals(host)) { - foundIntentFilterMatchingDomainName = true; - break; - } - } - } - return foundIntentFilterMatchingDomainName; + // 3. Verify the package name of app with Branch dash board settings + PackageNameCheck packageNameCheck = new PackageNameCheck(integrationModel, branchAppConfig); + result = packageNameCheck.RunTests(context); + integrationValidatorDialog.setTestResult(3, packageNameCheck.GetTestName(), result, packageNameCheck.GetOutput(context, result), packageNameCheck.GetMoreInfoLink()); + logOutputForTest(result, "3. Verifying application package name", packageNameCheck.errorMessage, "https://help.branch.io/developers-hub/docs/android-basic-integration#section-configure-branch-dashboard"); + + // 4. Verify the URI scheme setup + URISchemeCheck uriSchemeCheck = new URISchemeCheck(integrationModel, branchAppConfig); + result = uriSchemeCheck.RunTests(context); + integrationValidatorDialog.setTestResult(4, uriSchemeCheck.GetTestName(), result, uriSchemeCheck.GetOutput(context, result), uriSchemeCheck.GetMoreInfoLink()); + logOutputForTest(result, "4. Checking Android Manifest for URI based deep link config", uriSchemeCheck.errorMessage, "https://help.branch.io/developers-hub/docs/android-basic-integration#section-configure-app"); + + //5. Check AndroidManifest for AppLink config + AppLinksCheck appLinksCheck = new AppLinksCheck(integrationModel, branchAppConfig); + result = appLinksCheck.RunTests(context); + integrationValidatorDialog.setTestResult(5, appLinksCheck.GetTestName(), result, appLinksCheck.GetOutput(context, result), appLinksCheck.GetMoreInfoLink()); + logOutputForTest(result, "5. Checking AndroidManifest for AppLink config.", "Could not find any App Link hosts to support Android AppLinks. Please add intent filter for handling AppLinks in your Android Manifest file", "https://help.branch.io/using-branch/docs/android-app-links#section-add-intent-filter-to-manifest"); + + //6. Look for any custom domains specified in the dash board and has matching intent filter + CustomDomainCheck customDomainCheck = new CustomDomainCheck(integrationModel, branchAppConfig); + result = customDomainCheck.RunTests(context); + integrationValidatorDialog.setTestResult(6, customDomainCheck.GetTestName(), result, customDomainCheck.GetOutput(context, result), customDomainCheck.GetMoreInfoLink()); + logOutputForTest(result, "6. Verifying any supported custom link domains.", String.format("Could not find intent filter to support custom link domain '%s'. Please add intent filter for handling custom link domain in your Android Manifest file ", branchAppConfig.optString("short_url_domain")), "https://help.branch.io/developers-hub/docs/android-basic-integration#section-configure-app"); + + // 7. Check for matching intent filter for default app link domains + DefaultDomainsCheck defaultDomainsCheck = new DefaultDomainsCheck(integrationModel, branchAppConfig); + result = defaultDomainsCheck.RunTests(context); + integrationValidatorDialog.setTestResult(7, defaultDomainsCheck.GetTestName(), result, defaultDomainsCheck.GetOutput(context, result), defaultDomainsCheck.GetMoreInfoLink()); + logOutputForTest(result, "7. Verifying default link domains integrations.", String.format("Could not find intent filter to support Branch default link domain '%s'. Please add intent filter for handling custom link domain in your Android Manifest file ", branchAppConfig.optString("default_short_url_domain")), "https://help.branch.io/developers-hub/docs/android-basic-integration#section-configure-app"); + + // 8. Check for matching intent filter for alternate app link domains + AlternateDomainsCheck alternateDomainsCheck = new AlternateDomainsCheck(integrationModel, branchAppConfig); + result = alternateDomainsCheck.RunTests(context); + integrationValidatorDialog.setTestResult(8, alternateDomainsCheck.GetTestName(), result, alternateDomainsCheck.GetOutput(context, result), alternateDomainsCheck.GetMoreInfoLink()); + logOutputForTest(result, "8. Verifying alternate link domains integrations.", String.format("Could not find intent filter to support alternate link domain '%s'. Please add intent filter for handling custom link domain in your Android Manifest file ", branchAppConfig.optString("alternate_short_url_domain")), "https://help.branch.io/developers-hub/docs/android-basic-integration#section-configure-app"); + + finishTestingOutput(); + + WindowManager.LayoutParams lp = new WindowManager.LayoutParams(); + lp.copyFrom(Objects.requireNonNull(instance.integrationValidatorDialog.getWindow()).getAttributes()); + lp.width = WindowManager.LayoutParams.MATCH_PARENT; + lp.height = 1500; + instance.integrationValidatorDialog.show(); + instance.integrationValidatorDialog.getWindow().setAttributes(lp); } @Override public void onAppConfigAvailable(JSONObject branchAppConfig) { - if (branchAppConfig != null) { + if (branchAppConfig != null && !hasRan) { + hasRan = true; doValidateWithAppConfig(branchAppConfig); - } else { - logIntegrationError("Unable to read Dashboard config. Please confirm that your Branch key is properly added to the manifest. Please fix your Dashboard settings.", - "https://branch.app.link/link-settings-page"); + } else if(branchAppConfig == null){ + logIntegrationError("Unable to read Dashboard config. Please confirm that your Branch key is properly added to the manifest. Please fix your Dashboard settings.", "https://branch.app.link/link-settings-page"); } } @@ -248,5 +126,28 @@ private void logValidationPassed() { Log.d(TAG, "Passed"); } + private void logOutputForTest(boolean result, String progressMessage, String errorMessage, String documentLink) { + logValidationProgress(progressMessage); + if(result) { + logValidationPassed(); + } else { + logIntegrationError(errorMessage, documentLink); + hasTestFailed = true; + } + } + + private void finishTestingOutput() { + if(!hasTestFailed) { + Log.d(TAG, "--------------------------------------------\nSuccessfully completed Branch integration validation. Everything looks good!"); + Log.d(TAG, "\n Great! Comment out the 'validateSDKIntegration' line in your app. Next check your deep link routing.\n" + + " Append '?bnc_validate=true' to any of your app's Branch links and click it on your mobile device (not the Simulator!) to start the test.\n" + + " For instance, to validate a link like:\n" + + " https://.app.link/NdJ6nFzRbK\n" + + " click on:\n" + + " https://.app.link/NdJ6nFzRbK?bnc_validate=true"); + } else { + Log.d(TAG, "--------------------------------------------\nCompleted Branch integration validation. Almost there! Please correct the issues identified for your Branch SDK implementation."); + } + } } diff --git a/Branch-SDK/src/main/java/io/branch/referral/validators/IntegrationValidatorCheck.java b/Branch-SDK/src/main/java/io/branch/referral/validators/IntegrationValidatorCheck.java new file mode 100644 index 000000000..9cf7c60ee --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/IntegrationValidatorCheck.java @@ -0,0 +1,24 @@ +package io.branch.referral.validators; + +import android.content.Context; + +public abstract class IntegrationValidatorCheck { + String name; + String errorMessage; + String moreInfoLink; + + public abstract boolean RunTests(Context context); + + public String GetOutput(Context context, boolean didTestSucceed) { + String symbol = RunTests(context) ? IntegrationValidatorConstants.checkmark : IntegrationValidatorConstants.xmark; + return errorMessage; + } + + public String GetTestName() { + return name; + } + + public String GetMoreInfoLink() { + return moreInfoLink; + } +} diff --git a/Branch-SDK/src/main/java/io/branch/referral/validators/IntegrationValidatorConstants.java b/Branch-SDK/src/main/java/io/branch/referral/validators/IntegrationValidatorConstants.java new file mode 100644 index 000000000..05b84d30b --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/IntegrationValidatorConstants.java @@ -0,0 +1,6 @@ +package io.branch.referral.validators; + +public class IntegrationValidatorConstants { + public static final String checkmark = "✅"; + public static final String xmark = "❌"; +} diff --git a/Branch-SDK/src/main/java/io/branch/referral/validators/IntegrationValidatorDialog.java b/Branch-SDK/src/main/java/io/branch/referral/validators/IntegrationValidatorDialog.java new file mode 100644 index 000000000..43e033671 --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/IntegrationValidatorDialog.java @@ -0,0 +1,109 @@ +package io.branch.referral.validators; + +import android.app.Dialog; +import android.content.Context; +import android.content.Intent; +import android.view.Window; +import android.widget.Button; +import android.widget.TextView; + +import java.io.BufferedReader; +import java.io.InputStreamReader; + +import io.branch.referral.Branch; +import io.branch.referral.R; + +public class IntegrationValidatorDialog extends Dialog { + + IntegrationValidatorDialogRowItem test1RowItem; + IntegrationValidatorDialogRowItem test2RowItem; + IntegrationValidatorDialogRowItem test3RowItem; + IntegrationValidatorDialogRowItem test4RowItem; + IntegrationValidatorDialogRowItem test5RowItem; + IntegrationValidatorDialogRowItem test6RowItem; + IntegrationValidatorDialogRowItem test7RowItem; + IntegrationValidatorDialogRowItem test8RowItem; + + Button exportLogsButton; + + public IntegrationValidatorDialog(final Context context) { + super(context); + requestWindowFeature(Window.FEATURE_NO_TITLE); + this.setContentView(R.layout.dialog_integration_validator); + TextView sdkVersionTextView = findViewById(R.id.sdk_version); + sdkVersionTextView.setText("SDK Version: " + Branch.getSdkVersionNumber()); + + test1RowItem = findViewById(R.id.test_1_auto_instance_validator_row); + test2RowItem = findViewById(R.id.test_2_verify_branch_keys); + test3RowItem = findViewById(R.id.test_3_verify_package_name); + test4RowItem = findViewById(R.id.test_4_verify_uri_scheme); + test5RowItem = findViewById(R.id.test_5_verify_app_links); + test6RowItem = findViewById(R.id.test_6_verify_custom_domain); + test7RowItem = findViewById(R.id.test_7_domain_intent_filters); + test8RowItem = findViewById(R.id.test_8_alternate_domain_intent_filters); + + exportLogsButton = findViewById(R.id.export_logs_button); + + exportLogsButton.setOnClickListener(view -> { + shareLogsAsText(context); + }); + } + + public void setTestResult(int testNumber, String name, boolean didTestPass, String detailsMessage, String moreInfoLink) { + switch (testNumber) { + case 1: + setResult(test1RowItem, name, didTestPass, detailsMessage, moreInfoLink); + break; + case 2: + setResult(test2RowItem, name, didTestPass, detailsMessage, moreInfoLink); + break; + case 3: + setResult(test3RowItem, name, didTestPass, detailsMessage, moreInfoLink); + break; + case 4: + setResult(test4RowItem, name, didTestPass, detailsMessage, moreInfoLink); + break; + case 5: + setResult(test5RowItem, name, didTestPass, detailsMessage, moreInfoLink); + break; + case 6: + setResult(test6RowItem, name, didTestPass, detailsMessage, moreInfoLink); + break; + case 7: + setResult(test7RowItem, name, didTestPass, detailsMessage, moreInfoLink); + break; + case 8: + setResult(test8RowItem, name, didTestPass, detailsMessage, moreInfoLink); + break; + } + } + + private void setResult(IntegrationValidatorDialogRowItem rowItem, String name, boolean didTestPass, String detailsMessage, String moreInfoLink) { + rowItem.SetTitleText(name); + rowItem.SetTestResult(didTestPass); + rowItem.SetDetailsMessage(detailsMessage); + rowItem.SetMoreInfoLink(moreInfoLink); + } + + private void shareLogsAsText(Context context) { + try { + Process process = Runtime.getRuntime().exec("logcat -d BranchSDK:V *:S"); + BufferedReader bufferedReader = new BufferedReader( + new InputStreamReader(process.getInputStream())); + + StringBuilder log=new StringBuilder(); + String line = ""; + while ((line = bufferedReader.readLine()) != null) { + log.append(line + "\n"); + } + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, new String(log)); + sendIntent.setType("text/plain"); + Intent shareIntent = Intent.createChooser(sendIntent, null); + context.startActivity(shareIntent); + } catch (Exception e) { + System.out.println(e.getMessage()); + } + } +} diff --git a/Branch-SDK/src/main/java/io/branch/referral/validators/IntegrationValidatorDialogRowItem.java b/Branch-SDK/src/main/java/io/branch/referral/validators/IntegrationValidatorDialogRowItem.java new file mode 100644 index 000000000..9bb0cdc20 --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/IntegrationValidatorDialogRowItem.java @@ -0,0 +1,77 @@ +package io.branch.referral.validators; + +import android.app.AlertDialog; +import android.content.Context; +import android.text.Html; +import android.text.method.LinkMovementMethod; +import android.util.AttributeSet; +import android.view.Gravity; +import android.view.LayoutInflater; +import android.widget.Button; +import android.widget.LinearLayout; +import android.view.View; +import android.widget.TextView; + +import io.branch.referral.R; + +public class IntegrationValidatorDialogRowItem extends LinearLayout { + TextView titleText; + TextView testResultSymbol; + Button detailsButton; + String detailsMessage; + String moreInfoLink; + + public IntegrationValidatorDialogRowItem(Context context, AttributeSet attrs) { + super(context, attrs); + View view = LayoutInflater.from(getContext()).inflate( + R.layout.integration_validator_dialog_row_item, null); + this.addView(view); + titleText = view.findViewById(R.id.title_text); + testResultSymbol = view.findViewById(R.id.pass_or_fail_symbol_text); + detailsButton = view.findViewById(R.id.details_button); + detailsButton.setOnClickListener(view1 -> { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setMessage(detailsMessage + "\n"); + TextView hyperlinkToDocs = new TextView(context); + hyperlinkToDocs.setMovementMethod(LinkMovementMethod.getInstance()); + hyperlinkToDocs.setGravity(Gravity.CENTER_HORIZONTAL); + String link = ""; + hyperlinkToDocs.setText(Html.fromHtml(link)); + builder.setView(hyperlinkToDocs); + builder.setCancelable(false); + builder.setPositiveButton("OK", (dialog, which) -> {}); + AlertDialog alertDialog = builder.create(); + alertDialog.show(); + }); + } + + public IntegrationValidatorDialogRowItem(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public void SetTitleText(String title) { + titleText.setText(title); + } + + public void SetDetailsMessage(String detailsMessage) { + this.detailsMessage = detailsMessage; + } + + public void SetMoreInfoLink(String moreInfoLink) { + this.moreInfoLink = moreInfoLink; + } + + public void SetTestResult(boolean didTestPass) { + String result = didTestPass ? IntegrationValidatorConstants.checkmark : IntegrationValidatorConstants.xmark; + testResultSymbol.setText(result); + ToggleDetailsButton(didTestPass); + } + + public void ToggleDetailsButton(boolean didTestPass) { + if (didTestPass) { + detailsButton.setVisibility(INVISIBLE); + } else { + detailsButton.setVisibility(VISIBLE); + } + } +} diff --git a/Branch-SDK/src/main/java/io/branch/referral/validators/PackageNameCheck.java b/Branch-SDK/src/main/java/io/branch/referral/validators/PackageNameCheck.java new file mode 100644 index 000000000..e03261bf8 --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/PackageNameCheck.java @@ -0,0 +1,35 @@ +package io.branch.referral.validators; + +import android.content.Context; + +import org.json.JSONObject; + +public class PackageNameCheck extends IntegrationValidatorCheck { + + String name = "Package Name"; + String errorMessage = "Incorrect package name in Branch dashboard. Please correct your package name in dashboard -> Configuration page."; + String moreInfoLink = "More info"; + BranchIntegrationModel integrationModel; + JSONObject branchAppConfig; + + public PackageNameCheck(BranchIntegrationModel integrationModel, JSONObject branchAppConfig) { + super.name = name; + super.errorMessage = errorMessage; + super.moreInfoLink = moreInfoLink; + this.integrationModel = integrationModel; + this.branchAppConfig = branchAppConfig; + } + + @Override + public boolean RunTests(Context context) { + String valueOnDashboard = integrationModel.packageName; + String valueInManifest = branchAppConfig.optString("android_package_name"); + return valueOnDashboard.equals(valueInManifest); + } + + @Override + public String GetOutput(Context context, boolean didTestSucceed) { + didTestSucceed = RunTests(context); + return super.GetOutput(context, didTestSucceed); + } +} diff --git a/Branch-SDK/src/main/java/io/branch/referral/validators/URISchemeCheck.java b/Branch-SDK/src/main/java/io/branch/referral/validators/URISchemeCheck.java new file mode 100644 index 000000000..002abda34 --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/URISchemeCheck.java @@ -0,0 +1,91 @@ +package io.branch.referral.validators; + +import android.content.Context; +import android.net.Uri; +import android.text.TextUtils; + +import org.json.JSONArray; +import org.json.JSONObject; + +import java.util.Iterator; + +public class URISchemeCheck extends IntegrationValidatorCheck { + + String name = "URI Scheme"; + + String uriSchemeNotSetInManifestErrorMessage = "No intent found for opening the app through uri Scheme. Please add the intent with URI scheme to your Android manifest."; + String uriSchemeNotSetInBranchDashboardErrorMessage = "Uri Scheme to open your app is not specified in Branch dashboard. Please add URI scheme in Branch dashboard."; + String uriSchemesDoNotMatchErrorMessage = "Uri scheme specified in Branch dashboard doesn't match with the deep link intent in manifest file."; + + String moreInfoLinkApp = "More info"; + String moreInfoLinkDashboard = "More info"; + + BranchIntegrationModel integrationModel; + JSONObject branchAppConfig; + + public URISchemeCheck(BranchIntegrationModel integrationModel, JSONObject branchAppConfig) { + super.name = name; + super.errorMessage = ""; //defaulting to first check so it isn't empty before running tests + super.moreInfoLink = moreInfoLinkApp; //defaulting to first check so it isn't empty before running tests + this.integrationModel = integrationModel; + this.branchAppConfig = branchAppConfig; + } + + @Override + public boolean RunTests(Context context) { + String branchAppUriScheme = branchAppConfig.optString("android_uri_scheme").substring(0, branchAppConfig.optString("android_uri_scheme").length() - 3); + String dashboardUriScheme = String.valueOf(integrationModel.deeplinkUriScheme.keys().next()); + boolean isUriSchemeProperlySetOnDashboard = !TextUtils.isEmpty(branchAppUriScheme); + boolean isUriSchemeIntentProperlySetup = checkIfIntentAddedForURIScheme(branchAppConfig.optString("android_uri_scheme")) && integrationModel.appSettingsAvailable; + boolean doUriSchemesMatch = branchAppUriScheme.trim().equals(dashboardUriScheme.trim()); + + if(!isUriSchemeProperlySetOnDashboard) { + super.errorMessage = uriSchemeNotSetInBranchDashboardErrorMessage; + super.moreInfoLink = moreInfoLinkDashboard; + } + else if(!isUriSchemeIntentProperlySetup) { + super.errorMessage = uriSchemeNotSetInManifestErrorMessage; + super.moreInfoLink = moreInfoLinkApp; + } + else if(!doUriSchemesMatch) { + super.errorMessage = uriSchemesDoNotMatchErrorMessage; + super.moreInfoLink = moreInfoLinkApp; + } + + return doUriSchemesMatch && isUriSchemeProperlySetOnDashboard && isUriSchemeIntentProperlySetup; + } + + @Override + public String GetOutput(Context context, boolean didTestSucceed) { + didTestSucceed = RunTests(context); + return super.GetOutput(context, didTestSucceed); + } + + private boolean checkIfIntentAddedForURIScheme(String uriScheme) { + Uri branchDeepLinkURI = Uri.parse(uriScheme); + String uriHost = branchDeepLinkURI.getScheme(); + String uriPath = branchDeepLinkURI.getHost(); + uriPath = TextUtils.isEmpty(uriPath) ? "open" : uriPath; + boolean foundMatchingUri = false; + if (integrationModel.deeplinkUriScheme != null) { + for (Iterator it = integrationModel.deeplinkUriScheme.keys(); it.hasNext(); ) { + String key = it.next(); + if (uriHost != null && uriHost.equals(key)) { + JSONArray hosts = integrationModel.deeplinkUriScheme.optJSONArray(key); + if (hosts != null && hosts.length() > 0) { + for (int i = 0; i < hosts.length(); ++i) { + if (uriPath != null && uriPath.equals(hosts.optString(i))) { + foundMatchingUri = true; + break; + } + } + } else { + foundMatchingUri = true; + break; + } + } + } + } + return foundMatchingUri; + } +} diff --git a/Branch-SDK/src/main/res/drawable/branch_icon.png b/Branch-SDK/src/main/res/drawable/branch_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..bad163d2d3e772fe7448550d720ba56111b0d9bc GIT binary patch literal 6774 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 Y1KRB1Ku;LB5dZ)H07*qoM6N<$f*BObr2qf` literal 0 HcmV?d00001 diff --git a/Branch-SDK/src/main/res/layout/dialog_integration_validator.xml b/Branch-SDK/src/main/res/layout/dialog_integration_validator.xml new file mode 100644 index 000000000..d48372a5a --- /dev/null +++ b/Branch-SDK/src/main/res/layout/dialog_integration_validator.xml @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +