diff --git a/Branch-SDK-TestBed/src/main/java/io/branch/branchandroidtestbed/MainActivity.java b/Branch-SDK-TestBed/src/main/java/io/branch/branchandroidtestbed/MainActivity.java index cc7d1a4d2..b09547d3f 100644 --- a/Branch-SDK-TestBed/src/main/java/io/branch/branchandroidtestbed/MainActivity.java +++ b/Branch-SDK-TestBed/src/main/java/io/branch/branchandroidtestbed/MainActivity.java @@ -55,6 +55,7 @@ import io.branch.referral.util.LinkProperties; import io.branch.referral.util.ProductCategory; import io.branch.referral.util.ShareSheetStyle; +import io.branch.referral.validators.IntegrationValidator; public class MainActivity extends Activity { private EditText txtShortUrl; @@ -660,7 +661,7 @@ protected void onStart() { // Please look for "BranchSDK_Doctor" in the logcat to see the results. // IMP : Do not make this call in your production app - //IntegrationValidator.validate(MainActivity.this); + IntegrationValidator.validate(MainActivity.this); } 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/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..c9e6171dd --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/AlternateDomainsCheck.java @@ -0,0 +1,49 @@ +package io.branch.referral.validators; + +import static io.branch.referral.validators.IntegrationValidatorConstants.alternateDomainsMoreInfoDocsLink; + +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 = alternateDomainsMoreInfoDocsLink; + 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..e3a67d98b --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/AppLinksCheck.java @@ -0,0 +1,50 @@ +package io.branch.referral.validators; + +import static io.branch.referral.validators.IntegrationValidatorConstants.appLinksMoreInfoDocsLink; + +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 = appLinksMoreInfoDocsLink; + 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..41d9e3c54 --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/BranchInstanceCreationValidatorCheck.java @@ -0,0 +1,36 @@ +package io.branch.referral.validators; + +import static io.branch.referral.validators.IntegrationValidatorConstants.branchInstanceCreationMoreInfoDocsLink; + +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 = branchInstanceCreationMoreInfoDocsLink; + + 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..89c759822 --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/BranchKeysValidatorCheck.java @@ -0,0 +1,32 @@ +package io.branch.referral.validators; + +import static io.branch.referral.validators.IntegrationValidatorConstants.branchKeysMoreInfoDocsLink; + +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 = branchKeysMoreInfoDocsLink; + + 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..f1ef38a6d --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/CustomDomainCheck.java @@ -0,0 +1,50 @@ +package io.branch.referral.validators; + +import static io.branch.referral.validators.IntegrationValidatorConstants.customDomainMoreInfoDocsLink; + +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 = customDomainMoreInfoDocsLink; + 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..d9a2052dd --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/DefaultDomainsCheck.java @@ -0,0 +1,50 @@ +package io.branch.referral.validators; + +import static io.branch.referral.validators.IntegrationValidatorConstants.defaultDomainsMoreInfoDocsLink; + +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 = defaultDomainsMoreInfoDocsLink; + 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..9689f8b29 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,238 +1,132 @@ 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.interfaces.IBranchLoggingCallbacks; import io.branch.referral.Branch; -import io.branch.referral.BranchUtil; - -/** - * Created by sojanpr on 9/15/17. - */ public class IntegrationValidator implements ServerRequestGetAppConfig.IGetAppConfigEvents { private static IntegrationValidator instance; private final BranchIntegrationModel integrationModel; private final String TAG = "BranchSDK_Doctor"; + private final StringBuilder branchLogsStringBuilder; + + Context context; + + boolean hasRan = false; + boolean hasTestFailed = false; + + IntegrationValidatorDialog integrationValidatorDialog; private IntegrationValidator(Context context) { this.integrationModel = new BranchIntegrationModel(context); + this.context = context; + this.branchLogsStringBuilder = new StringBuilder(); } public static void validate(Context context) { if (instance == null) { instance = new IntegrationValidator(context); } + + IBranchLoggingCallbacks iBranchLoggingCallbacks = new IBranchLoggingCallbacks() { + @Override + public void onBranchLog(String logMessage, String severityConstantName) { + instance.branchLogsStringBuilder.append(logMessage); + } + }; + + Branch.enableLogging(iBranchLoggingCallbacks); 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)); + public static String getLogs() { + return instance.branchLogsStringBuilder.toString(); + } + private void validateSDKIntegration(Context context) { + 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.SetTestResultForRowItem(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.SetTestResultForRowItem(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.SetTestResultForRowItem(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.SetTestResultForRowItem(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.SetTestResultForRowItem(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.SetTestResultForRowItem(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.SetTestResultForRowItem(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.SetTestResultForRowItem(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 +142,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..87dbc8d54 --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/IntegrationValidatorConstants.java @@ -0,0 +1,15 @@ +package io.branch.referral.validators; + +public class IntegrationValidatorConstants { + public static final String checkmark = "✅"; + public static final String xmark = "❌"; + public static final String appLinksMoreInfoDocsLink = "More info"; + public static final String alternateDomainsMoreInfoDocsLink = "More info"; + public static final String branchInstanceCreationMoreInfoDocsLink = "More info"; + public static final String branchKeysMoreInfoDocsLink = "More info"; + public static final String customDomainMoreInfoDocsLink = "More info"; + public static final String defaultDomainsMoreInfoDocsLink = "More info"; + public static final String packageNameMoreInfoDocsLink = "More info"; + public static final String uriSchemeAppMoreInfoDocsLink = "More info"; + public static final String uriSchemeDashboardMoreInfoDocsLink = "More info"; +} 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..931a2e4c9 --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/IntegrationValidatorDialog.java @@ -0,0 +1,99 @@ +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 io.branch.referral.Branch; +import io.branch.referral.R; + +public class IntegrationValidatorDialog extends Dialog { + + IntegrationValidatorDialogRowItem autoInstanceValidatorRowItem; + IntegrationValidatorDialogRowItem verifyBranchKeysRowItem; + IntegrationValidatorDialogRowItem verifyPackageNameRowItem; + IntegrationValidatorDialogRowItem verifyURISchemeRowItem; + IntegrationValidatorDialogRowItem verifyAppLinksRowItem; + IntegrationValidatorDialogRowItem verifyCustomDomainRowItem; + IntegrationValidatorDialogRowItem domainIntentFiltersRowItem; + IntegrationValidatorDialogRowItem alternateDomainIntentFiltersRowItem; + + Button exportLogsButton; + Button testDeepLinkingButton; + + 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()); + + autoInstanceValidatorRowItem = findViewById(R.id.test_1_auto_instance_validator_row); + verifyBranchKeysRowItem = findViewById(R.id.test_2_verify_branch_keys); + verifyPackageNameRowItem = findViewById(R.id.test_3_verify_package_name); + verifyURISchemeRowItem = findViewById(R.id.test_4_verify_uri_scheme); + verifyAppLinksRowItem = findViewById(R.id.test_5_verify_app_links); + verifyCustomDomainRowItem = findViewById(R.id.test_6_verify_custom_domain); + domainIntentFiltersRowItem = findViewById(R.id.test_7_domain_intent_filters); + alternateDomainIntentFiltersRowItem = findViewById(R.id.test_8_alternate_domain_intent_filters); + + exportLogsButton = findViewById(R.id.export_logs_button); + testDeepLinkingButton = findViewById(R.id.test_deep_linking_button); + + exportLogsButton.setOnClickListener(view -> { + shareLogsAsText(context); + }); + + testDeepLinkingButton.setOnClickListener(view -> { + LinkingValidator.validate(context); + }); + } + + public void SetTestResultForRowItem(int testNumber, String name, boolean didTestPass, String detailsMessage, String moreInfoLink) { + switch (testNumber) { + case 1: + setResult(autoInstanceValidatorRowItem, name, didTestPass, detailsMessage, moreInfoLink); + break; + case 2: + setResult(verifyBranchKeysRowItem, name, didTestPass, detailsMessage, moreInfoLink); + break; + case 3: + setResult(verifyPackageNameRowItem, name, didTestPass, detailsMessage, moreInfoLink); + break; + case 4: + setResult(verifyURISchemeRowItem, name, didTestPass, detailsMessage, moreInfoLink); + break; + case 5: + setResult(verifyAppLinksRowItem, name, didTestPass, detailsMessage, moreInfoLink); + break; + case 6: + setResult(verifyCustomDomainRowItem, name, didTestPass, detailsMessage, moreInfoLink); + break; + case 7: + setResult(domainIntentFiltersRowItem, name, didTestPass, detailsMessage, moreInfoLink); + break; + case 8: + setResult(alternateDomainIntentFiltersRowItem, 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) { + Intent sendIntent = new Intent(); + sendIntent.setAction(Intent.ACTION_SEND); + sendIntent.putExtra(Intent.EXTRA_TEXT, IntegrationValidator.getLogs()); + sendIntent.setType("text/plain"); + Intent shareIntent = Intent.createChooser(sendIntent, null); + context.startActivity(shareIntent); + } +} 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/LinkingValidator.java b/Branch-SDK/src/main/java/io/branch/referral/validators/LinkingValidator.java new file mode 100644 index 000000000..919f996f1 --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/LinkingValidator.java @@ -0,0 +1,31 @@ +package io.branch.referral.validators; + +import android.content.Context; +import android.view.WindowManager; +import android.widget.Button; + +import java.util.Objects; + +public class LinkingValidator { + private static LinkingValidator instance; + private LinkingValidatorDialog linkingValidatorDialog; + + private LinkingValidator(Context context) {} + + public static void validate(Context context) { + if (instance == null) { + instance = new LinkingValidator(context); + } + instance.linkingValidatorDialog = new LinkingValidatorDialog(context); + instance.validateDeepLinkRouting(context); + } + + private void validateDeepLinkRouting(Context context) { + WindowManager.LayoutParams lp = new WindowManager.LayoutParams(); + lp.copyFrom(Objects.requireNonNull(instance.linkingValidatorDialog.getWindow()).getAttributes()); + lp.width = WindowManager.LayoutParams.MATCH_PARENT; + lp.height = 2000; + instance.linkingValidatorDialog.show(); + instance.linkingValidatorDialog.getWindow().setAttributes(lp); + } +} diff --git a/Branch-SDK/src/main/java/io/branch/referral/validators/LinkingValidatorConstants.java b/Branch-SDK/src/main/java/io/branch/referral/validators/LinkingValidatorConstants.java new file mode 100644 index 000000000..c237322d4 --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/LinkingValidatorConstants.java @@ -0,0 +1,35 @@ +package io.branch.referral.validators; + +public class LinkingValidatorConstants { + public static final String canonicalURLPromptText = "Please paste in a web link for the $canonical_url"; + public static final String deeplinkPathPromptText = "Please paste in a value for the $deeplink_path"; + public static final String customKeyPromptText = "Please enter your custom key and value for routing"; + public static final String step1ButtonText = "Next"; + public static final String step2ButtonText = " Generate Links for Testing "; + public static final String step3ButtonText = "Done"; + public static final String canonicalUrlKey = "$canonical_url"; + public static final String deeplinkPathKey = "$deeplink_path"; + + public static final String linkingValidatorRow1Title = "Link using App Link"; + public static final String linkingValidatorRow2Title = "Link using URI scheme"; + public static final String linkingValidatorRow3Title = "Web-only link"; + public static final String linkingValidatorRow4Title = "Link with missing data"; + public static final String linkingValidatorRow5Title = "Warm start use case"; + public static final String linkingValidatorRow6Title = "Foreground click use case"; + + public static final String infoButton1Text = "Verifies that Universal Links / App Links are working correctly for your Branch domain"; + public static final String infoButton2Text = "Verifies that URI schemes work correctly for your Branch domain"; + public static final String infoButton3Text = "Verifies that web-only links are handled correctly to take you to the mobile web"; + public static final String infoButton4Text = "Verifies that your app gracefully handles Branch links missing deep link data"; + public static final String infoButton5Text = "Click the button to simulate a deep link click for the warm start use case"; + public static final String infoButton6Text = "Click the button to simulate a deep link click for the foreground use case"; + + public static final String debugButton1Text = "Ensure you’ve entered the correct SHA 256s on the dashboard and added your Branch domains to the Android Manifest"; + public static final String debugButton2Text = "Ensure that you’ve added a unique Branch URI scheme to the dashboard and Android Manifest"; + public static final String debugButton3Text = "Ensure that your code checks for $web-only in the link data, and if it is true routes the user to the mobile web"; + public static final String debugButton4Text = "Ensure that your code gracefully handles missing or invalid deep link data like taking them to the home screen"; + public static final String debugButton5Text = "Ensure that you are initializing Branch inside of onStart() and that the code is called anytime the app enters the foreground"; + public static final String debugButton6Text = "Ensure that you are calling reInit() inside of onNewIntent() after checking if branch_force_new_session is true"; + + +} diff --git a/Branch-SDK/src/main/java/io/branch/referral/validators/LinkingValidatorDialog.java b/Branch-SDK/src/main/java/io/branch/referral/validators/LinkingValidatorDialog.java new file mode 100644 index 000000000..e228965b8 --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/LinkingValidatorDialog.java @@ -0,0 +1,171 @@ +package io.branch.referral.validators; + +import android.app.Dialog; +import android.content.Context; +import android.view.View; +import android.view.Window; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.Spinner; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; + +import io.branch.referral.R; + +public class LinkingValidatorDialog extends Dialog implements AdapterView.OnItemSelectedListener { + + private enum ROUTING_TYPE { CANONICAL_URL, DEEPLINK_PATH, CUSTOM } + private ROUTING_TYPE routingType; + private final Button ctaButton; + private final Spinner linkingValidatorDropdownMenu; + private final TextView linkingValidatorText; + private final EditText linkingValidatorEditText; + private final LinearLayout customKVPField; + private final LinearLayout linkingValidatorRowsLayout; + private int step = 1; + private String routingKey = ""; + private String routingValue = ""; + private LinkingValidatorDialogRowItem row1; + private LinkingValidatorDialogRowItem row2; + private LinkingValidatorDialogRowItem row3; + private LinkingValidatorDialogRowItem row4; + private LinkingValidatorDialogRowItem row5; + private LinkingValidatorDialogRowItem row6; + + public LinkingValidatorDialog(final Context context) { + super(context); + requestWindowFeature(Window.FEATURE_NO_TITLE); + this.setContentView(R.layout.dialog_linking_validator); + + linkingValidatorDropdownMenu = findViewById(R.id.linkingValidatorDropdownMenu); + List choices = new ArrayList<>(); + choices.add(LinkingValidatorConstants.canonicalUrlKey); + choices.add(LinkingValidatorConstants.deeplinkPathKey); + choices.add("other (custom)"); + ArrayAdapter dataAdapter = new ArrayAdapter<>(context, android.R.layout.simple_spinner_item, choices); + dataAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + linkingValidatorDropdownMenu.setAdapter(dataAdapter); + linkingValidatorDropdownMenu.setOnItemSelectedListener(this); + + ctaButton = findViewById(R.id.linkingValidatorButton); + ctaButton.setText(LinkingValidatorConstants.step1ButtonText); + ctaButton.setOnClickListener(view -> { + switch(step) { + case 1: + LoadStep2Screen(); + break; + case 2: + GenerateBranchLinks(); + break; + case 3: + CloseDialog(); + break; + } + }); + + linkingValidatorText = findViewById(R.id.linkingValidatorText); + linkingValidatorEditText = findViewById(R.id.linkingValidatorEditText); + customKVPField = findViewById(R.id.customKVPField); + linkingValidatorRowsLayout = findViewById(R.id.linkingValidatorRows); + + linkingValidatorEditText.setVisibility(View.GONE); + customKVPField.setVisibility(View.GONE); + linkingValidatorRowsLayout.setVisibility(View.GONE); + + routingType = ROUTING_TYPE.CANONICAL_URL; + + row1 = findViewById(R.id.linkingValidatorRow1); + row2 = findViewById(R.id.linkingValidatorRow2); + row3 = findViewById(R.id.linkingValidatorRow3); + row4 = findViewById(R.id.linkingValidatorRow4); + row5 = findViewById(R.id.linkingValidatorRow5); + row6 = findViewById(R.id.linkingValidatorRow6); + } + + @Override + public void onItemSelected(AdapterView adapterView, View view, int i, long l) { + String item = adapterView.getItemAtPosition(i).toString(); + switch (item) { + case "$canonical_url": + routingType = ROUTING_TYPE.CANONICAL_URL; + break; + case "$deeplink_path": + routingType = ROUTING_TYPE.DEEPLINK_PATH; + break; + case "other (custom)": + routingType = ROUTING_TYPE.CUSTOM; + break; + } + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + + } + + void LoadStep2Screen() { + step++; + ctaButton.setText(LinkingValidatorConstants.step2ButtonText); + linkingValidatorDropdownMenu.setVisibility(View.GONE); + + switch(routingType) { + case CANONICAL_URL: + linkingValidatorEditText.setVisibility(View.VISIBLE); + linkingValidatorText.setText(LinkingValidatorConstants.canonicalURLPromptText); + break; + case DEEPLINK_PATH: + linkingValidatorEditText.setVisibility(View.VISIBLE); + linkingValidatorText.setText(LinkingValidatorConstants.deeplinkPathPromptText); + break; + case CUSTOM: + customKVPField.setVisibility(View.VISIBLE); + linkingValidatorText.setText(LinkingValidatorConstants.customKeyPromptText); + break; + } + } + + void GenerateBranchLinks() { + switch(routingType) { + case CANONICAL_URL: + routingKey = LinkingValidatorConstants.canonicalUrlKey; + routingValue = linkingValidatorEditText.getText().toString(); + break; + case DEEPLINK_PATH: + routingKey = LinkingValidatorConstants.deeplinkPathKey; + routingValue = linkingValidatorEditText.getText().toString(); + break; + } + + step++; + linkingValidatorEditText.setVisibility(View.GONE); + customKVPField.setVisibility(View.GONE); + linkingValidatorText.setVisibility(View.GONE); + ctaButton.setText(LinkingValidatorConstants.step3ButtonText); + linkingValidatorRowsLayout.setVisibility(View.VISIBLE); + + EditText customKeyEditText = findViewById(R.id.keyEditText); + EditText customValueEditText = findViewById(R.id.valueEditText); + + //if routing key is empty, it is a custom key outside of $canonical_url and $deeplink_path + if(routingKey.isEmpty()) { + routingKey = customKeyEditText.getText().toString(); + routingValue = customValueEditText.getText().toString(); + } + + row1.InitializeRow(LinkingValidatorConstants.linkingValidatorRow1Title, LinkingValidatorConstants.infoButton1Text, LinkingValidatorConstants.debugButton1Text, routingKey, routingValue, "regularBranchLink", true, 0); + row2.InitializeRow(LinkingValidatorConstants.linkingValidatorRow2Title, LinkingValidatorConstants.infoButton2Text, LinkingValidatorConstants.debugButton2Text, routingKey, routingValue, "uriFallbackBranchLink", true, 1, "$uri_redirect_mode", "2"); + row3.InitializeRow(LinkingValidatorConstants.linkingValidatorRow3Title, LinkingValidatorConstants.infoButton3Text, LinkingValidatorConstants.debugButton3Text, routingKey, routingValue, "webOnlyBranchLink", true, 2, "$web_only", "true"); + row4.InitializeRow(LinkingValidatorConstants.linkingValidatorRow4Title, LinkingValidatorConstants.infoButton4Text, LinkingValidatorConstants.debugButton4Text, routingKey, "", "missingDataBranchLink", true, 3); + row5.InitializeRow(LinkingValidatorConstants.linkingValidatorRow5Title, LinkingValidatorConstants.infoButton5Text, LinkingValidatorConstants.debugButton5Text, routingKey, routingValue, "warmStartUseCase", false, 4); + row6.InitializeRow(LinkingValidatorConstants.linkingValidatorRow6Title, LinkingValidatorConstants.infoButton6Text, LinkingValidatorConstants.debugButton6Text, routingKey, routingValue, "foregroundClickUseCase", false, 5); + } + + private void CloseDialog() { + this.dismiss(); + } +} diff --git a/Branch-SDK/src/main/java/io/branch/referral/validators/LinkingValidatorDialogRowItem.java b/Branch-SDK/src/main/java/io/branch/referral/validators/LinkingValidatorDialogRowItem.java new file mode 100644 index 000000000..0c8d961a5 --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/LinkingValidatorDialogRowItem.java @@ -0,0 +1,169 @@ +package io.branch.referral.validators; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.ContextWrapper; +import android.content.Intent; +import android.os.Build; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.util.HashMap; + +import io.branch.indexing.BranchUniversalObject; +import io.branch.referral.Branch; +import io.branch.referral.BranchError; +import io.branch.referral.R; +import io.branch.referral.util.LinkProperties; + +public class LinkingValidatorDialogRowItem extends LinearLayout { + + private static final String TAG = "BranchSDK"; + + TextView titleText; + Button infoButton; + String infoText; + Button actionButton; + HashMap linkDataParams; + String routingKey; + String routingValue; + String canonicalIdentifier; + Context context; + Button debugButton; + String debugText; + + public LinkingValidatorDialogRowItem(Context context, AttributeSet attrs) { + super(context, attrs); + this.context = context; + } + + public LinkingValidatorDialogRowItem(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + this.context = context; + } + + public void InitializeRow(String title, String infoText, String debugText, String routingKey, String routingValue, String canonicalIdentifier, boolean isSharableLink, int index, String... params) { + View view = LayoutInflater.from(getContext()).inflate(R.layout.linking_validator_dialog_row_item, null); + this.addView(view); + titleText = view.findViewById(R.id.linkingValidatorRowTitleText); + infoButton = view.findViewById(R.id.linkingValidatorRowInfoButton); + actionButton = view.findViewById(R.id.linkingValidatorRowActionButton); + debugButton = view.findViewById(R.id.linkingValidatorRowDebugButton); + + titleText.setText(title); + this.infoText = infoText; + this.debugText = debugText; + this.routingKey = routingKey; + this.routingValue = routingValue; + this.canonicalIdentifier = canonicalIdentifier; + + linkDataParams = new HashMap<>(); + + for (int i = 0; i < params.length; i += 2) { + linkDataParams.put(params[i], params[i + 1]); + } + + linkDataParams.put(routingKey, routingValue); + + infoButton.setOnClickListener(view1 -> { + HandleInfoButtonClicked(); + }); + + debugButton.setOnClickListener(view2 -> { + HandleDebugButtonClicked(); + }); + + if (isSharableLink) { + actionButton.setText("Share"); + + actionButton.setOnClickListener(view2 -> { + HandleShareButtonClicked(); + }); + } else { + actionButton.setText("Test"); + + if (index == 4) { + actionButton.setOnClickListener(view2 -> { + HandleWarmStartClick(); + }); + } else if (index == 5) { + actionButton.setOnClickListener(view2 -> { + HandleForegroundLinkClick(); + }); + } + } + } + + private void HandleInfoButtonClicked() { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setMessage(infoText).setTitle(titleText.getText()); + AlertDialog dialog = builder.create(); + dialog.show(); + } + + private void HandleShareButtonClicked() { + LinkProperties lp = new LinkProperties(); + for (String key : linkDataParams.keySet()) { + lp.addControlParameter(key, linkDataParams.get(key)); + } + BranchUniversalObject buo = new BranchUniversalObject().setCanonicalIdentifier(canonicalIdentifier); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { + Branch.getInstance().share(getActivity(context), buo, lp, new Branch.BranchNativeLinkShareListener() { + @Override + public void onLinkShareResponse(String sharedLink, BranchError error) { + } + + @Override + public void onChannelSelected(String channelName) { + } + }, + titleText.getText().toString(), + infoText); + } + } + + private void HandleDebugButtonClicked() { + AlertDialog.Builder builder = new AlertDialog.Builder(context); + builder.setMessage(debugText).setTitle(titleText.getText() + " not working?"); + AlertDialog dialog = builder.create(); + dialog.show(); + } + + private void HandleWarmStartClick() { + getActivity(context).moveTaskToBack(true); + HandleForegroundLinkClick(); + } + + private void HandleForegroundLinkClick() { + BranchUniversalObject buo = new BranchUniversalObject().setCanonicalIdentifier(canonicalIdentifier); + LinkProperties lp = new LinkProperties(); + lp.addControlParameter(routingKey, routingValue); + for (int i = 0; i < linkDataParams.size(); i += 2) { + lp.addControlParameter(linkDataParams.get(i), linkDataParams.get(i + 1)); + } + String branchLink = buo.getShortUrl(context, lp); + Intent intent = new Intent(getContext(), getActivity(context).getClass()); + intent.putExtra("branch", branchLink); + intent.putExtra("branch_force_new_session", true); + getActivity(context).startActivity(intent); + } + + public Activity getActivity(Context context) { + if (context == null) { + return null; + } else if (context instanceof ContextWrapper) { + if (context instanceof Activity) { + return (Activity) context; + } else { + return getActivity(((ContextWrapper) context).getBaseContext()); + } + } + + return null; + } +} 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..2adc27d90 --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/PackageNameCheck.java @@ -0,0 +1,37 @@ +package io.branch.referral.validators; + +import static io.branch.referral.validators.IntegrationValidatorConstants.packageNameMoreInfoDocsLink; + +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 = packageNameMoreInfoDocsLink; + 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..d8da08d59 --- /dev/null +++ b/Branch-SDK/src/main/java/io/branch/referral/validators/URISchemeCheck.java @@ -0,0 +1,94 @@ +package io.branch.referral.validators; + +import static io.branch.referral.validators.IntegrationValidatorConstants.uriSchemeAppMoreInfoDocsLink; +import static io.branch.referral.validators.IntegrationValidatorConstants.uriSchemeDashboardMoreInfoDocsLink; + +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 = uriSchemeAppMoreInfoDocsLink; + String moreInfoLinkDashboard = uriSchemeDashboardMoreInfoDocsLink; + + 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 000000000..bad163d2d Binary files /dev/null and b/Branch-SDK/src/main/res/drawable/branch_icon.png differ 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..b15b662be --- /dev/null +++ b/Branch-SDK/src/main/res/layout/dialog_integration_validator.xml @@ -0,0 +1,169 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +