diff --git a/hooks/lib/android/androidManifest.js b/hooks/lib/android/androidManifest.js index 3e449c0f..4bb7791a 100644 --- a/hooks/lib/android/androidManifest.js +++ b/hooks/lib/android/androidManifest.js @@ -15,9 +15,11 @@ var pathToManifest = path.join(context.opts.projectRoot, 'platforms', 'android', 'AndroidManifest.xml') var manifest = xmlHelper.readXmlAsJson(pathToManifest) + // TODO: early exit + console.log('BRANCH SDK: Updating AndroidManifest.xml') + // update manifest - manifest = removePreviousOptions(manifest) manifest = updateBranchKeyMetaData(manifest, preferences) manifest = updateBranchReferrerTracking(manifest) manifest = updateLaunchOptionToSingleTask(manifest, preferences) @@ -28,11 +30,62 @@ xmlHelper.writeJsonAsXml(manifest, pathToManifest) } + function updateBranchKeyMetaData (manifest, preferences) { + var metadatas = manifest['manifest']['application'][0]['meta-data'] || [] + var androidName = 'io.branch.sdk.BranchKey' + + // remove old + metadatas = removeBasedOnAndroidName(metadatas, androidName) + + // add new + manifest['manifest']['application'][0]['meta-data'] = metadatas.concat([{ + '$': { + 'android:name': androidName, + 'android:value': preferences.branchKey + } + }]) + + return manifest + } + + function updateBranchReferrerTracking (manifest) { + var receivers = manifest['manifest']['application'][0]['receiver'] || [] + var androidName = 'io.branch.referral.InstallListener' + + // remove old + receivers = removeBasedOnAndroidName(receivers, androidName) + + // add new + manifest['manifest']['application'][0]['receiver'] = receivers.concat([{ + '$': { + 'android:name': androidName, + 'android:exported': true + }, + 'intent-filter': [{ + 'action': [{ + '$': { + 'android:name': 'com.android.vending.INSTALL_REFERRER' + } + }] + }] + }]) + + return manifest + } + + function updateLaunchOptionToSingleTask (manifest, preferences) { + manifest['manifest']['application'][0]['activity'][0]['$']['android:launchMode'] = 'singleTask' + return manifest + } + function updateBranchURIScheme (manifest, preferences) { - var intentFilter = manifest['manifest']['application'][0]['activity'][0]['intent-filter'] || [] // TODO: need to validate main activity (second [0]) + var intentFilters = manifest['manifest']['application'][0]['activity'][0]['intent-filter'] || [] - manifest['manifest']['application'][0]['activity'][0]['intent-filter'] = intentFilter.concat([{ + intentFilters = removeBasedOnIntentFilter(intentFilters) + + // add new + manifest['manifest']['application'][0]['activity'][0]['intent-filter'] = intentFilters.concat([{ 'action': [{ '$': { 'android:name': 'android.intent.action.VIEW' @@ -58,147 +111,117 @@ return manifest } - function updateLaunchOptionToSingleTask (manifest, preferences) { - manifest['manifest']['application'][0]['activity'][0]['$']['android:launchMode'] = 'singleTask' - return manifest - } - - function updateBranchReferrerTracking (manifest) { - var receiver = manifest['manifest']['application'][0]['receiver'] || [] + function updateBranchAppLinks (manifest, preferences) { + var intentFilters = manifest['manifest']['application'][0]['activity'][0]['intent-filter'] || [] + var data = getAppLinkIntentFilterData(preferences) - manifest['manifest']['application'][0]['receiver'] = receiver.concat([{ + // remove old (already done in updateBranchURIScheme) + // add new + manifest['manifest']['application'][0]['activity'][0]['intent-filter'] = intentFilters.concat([{ '$': { - 'android:name': 'io.branch.referral.InstallListener', - 'android:exported': true + 'android:autoVerify': 'true' }, - 'intent-filter': [{ - 'action': [{ - '$': { - 'android:name': 'com.android.vending.INSTALL_REFERRER' - } - }] - }] - }]) - - return manifest - } - - function updateBranchKeyMetaData (manifest, preferences) { - var metadata = manifest['manifest']['application'][0]['meta-data'] || [] - - // loop through - // if exists, update - // if not, append - manifest['manifest']['application'][0]['meta-data'] = metadata.concat([{ - '$': { - 'android:name': 'io.branch.sdk.BranchKey', - 'android:value': preferences.branchKey - } + 'action': [{ + '$': { + 'android:name': 'android.intent.action.VIEW' + } + }], + 'category': [{ + '$': { + 'android:name': 'android.intent.category.DEFAULT' + } + }, { + '$': { + 'android:name': 'android.intent.category.BROWSABLE' + } + }], + 'data': data }]) return manifest } - function removePreviousOptions (manifest) { - var activities = manifest['manifest']['application'][0]['activity'] - - activities.forEach(removeIntentFiltersFromActivity) - manifest['manifest']['application'][0]['activity'] = activities - - return manifest - } - - function removeIntentFiltersFromActivity (activity) { - var oldIntentFilters = activity['intent-filter'] - var newIntentFilters = [] + function getAppLinkIntentFilterData (preferences) { + var intentFilterData = [] - if (oldIntentFilters == null || oldIntentFilters.length === 0) { - return - } + if (preferences.linkDomain.indexOf('app.link') !== -1) { + // app.link + var first = preferences.linkDomain.split('.')[0] + var rest = preferences.linkDomain.split('.').slice(2).join('.') + var alternate = first + '-alternate' + '.' + rest - oldIntentFilters.forEach(function (intentFilter) { - if (!isIntentFilterForUniversalLinks(intentFilter)) { - newIntentFilters.push(intentFilter) + intentFilterData.push(getAppLinkIntentFilterDictionary(preferences.linkDomain)) + intentFilterData.push(getAppLinkIntentFilterDictionary(alternate)) + } else if (preferences.linkDomain.indexOf('bnc.lt') !== -1) { + // bnc.lt + if (preferences.androidPrefix == null) { + throw new Error('Branch SDK plugin is missing "android-prefix" in in your config.xml') } - }) - - activity['intent-filter'] = newIntentFilters - } - function isIntentFilterForUniversalLinks (intentFilter) { - var actions = intentFilter['action'] - var categories = intentFilter['category'] - var data = intentFilter['data'] + intentFilterData.push(getAppLinkIntentFilterDictionary(preferences.linkDomain, preferences.androidPrefix)) + } else { + // custom + intentFilterData.push(getAppLinkIntentFilterDictionary(preferences.linkDomain)) + } - return isActionForUniversalLinks(actions) && isCategoriesForUniversalLinks(categories) && isDataTagForUniversalLinks(data) + return intentFilterData } - function isActionForUniversalLinks (actions) { - // there can be only 1 action - if (actions == null || actions.length !== 1) { - return false + function getAppLinkIntentFilterDictionary (linkDomain, androidPrefix) { + var scheme = 'https' + var output = { + '$': { + 'android:host': linkDomain, + 'android:scheme': scheme + } } - var action = actions[0]['$']['android:name'] - - return action === 'android.intent.action.VIEW' - } - - function isCategoriesForUniversalLinks (categories) { - // there can be only 2 categories - if (categories == null || categories.length !== 2) { - return false + if (androidPrefix) { + output['$']['android:pathPrefix'] = androidPrefix } - var isBrowsable = false - var isDefault = false + return output + } - // check intent categories - categories.forEach(function (category) { - var categoryName = category['$']['android:name'] - if (!isBrowsable) { - isBrowsable = categoryName === 'android.intent.category.BROWSABLE' - } + function removeBasedOnIntentFilter (items) { + var without = [] + for (var i = 0; i < items.length; i++) { + var item = items[i] + if (item.hasOwnProperty('action') && item.hasOwnProperty('category') && item.hasOwnProperty('data')) { + var actions = item['action'] + var categories = item['category'] + var data = item['data'] + + if (actions.length === 1 && actions[0]['$'].hasOwnProperty('android:name') && actions[0]['$']['android:name'] === 'android.intent.action.VIEW' && categories.length === 2 && categories[0]['$'].hasOwnProperty('android:name') && (categories[0]['$']['android:name'] === 'android.intent.category.DEFAULT' || categories[0]['$']['android:name'] === 'android.intent.category.BROWSABLE') && categories[1]['$'].hasOwnProperty('android:name') && (categories[1]['$']['android:name'] === 'android.intent.category.DEFAULT' || categories[1]['$']['android:name'] === 'android.intent.category.BROWSABLE') && data.length > 0 && data.length < 3) { + // URI Scheme + if (data[0]['$'].hasOwnProperty('android:scheme') && data[0]['$'].hasOwnProperty('android:host') && data[0]['$']['android:host'] === 'open' && !item.hasOwnProperty('$')) { + continue + } - if (!isDefault) { - isDefault = categoryName === 'android.intent.category.DEFAULT' + // AppLink + if (data[0]['$'].hasOwnProperty('android:host') && data[0]['$'].hasOwnProperty('android:scheme') && data[0]['$']['android:scheme'] === 'https' && item.hasOwnProperty('$') && item['$'].hasOwnProperty('android:autoVerify') && item['$']['android:autoVerify'] === 'true') { + continue + } + } } - }) - - return isDefault && isBrowsable - } - - function isDataTagForUniversalLinks (data) { - // can have only 1 data tag in the intent-filter - if (data == null || data.length !== 1) { - return false + without.push(item) } - - var dataHost = data[0]['$']['android:host'] - var dataScheme = data[0]['$']['android:scheme'] - var hostIsSet = dataHost != null && dataHost.length > 0 - var schemeIsSet = dataScheme != null && dataScheme.length > 0 - - return hostIsSet && schemeIsSet + return without } - function updateBranchAppLinks (manifest, preferences) { - var activitiesList = manifest['manifest']['application'][0]['activity'] - var launchActivityIndex = getMainLaunchActivityIndex(activitiesList) - var intentFilters = createAppLinkIntentFilter(preferences) - var launchActivity - - if (launchActivityIndex < 0) { - throw new Error('BRANCH SDK - Could not find launch activity in the AndroidManifest file. Can\'t inject Universal Links preferences.') + function removeBasedOnAndroidName (items, androidName) { + var without = [] + for (var i = 0; i < items.length; i++) { + var item = items[i] + if (item.hasOwnProperty('$') && item['$'].hasOwnProperty('android:name')) { + var key = item['$']['android:name'] + if (key === androidName) { + continue + } + without.push(item) + } } - - // get launch activity - launchActivity = activitiesList[launchActivityIndex] - - // add Universal Links intent-filters to the launch activity - launchActivity['intent-filter'] = launchActivity['intent-filter'].concat(intentFilters) - - return manifest + return without } function getMainLaunchActivityIndex (activities) { @@ -240,80 +263,4 @@ return isLauncher } - - function createAppLinkIntentFilter (preferences) { - var data = getAppLinkIntentFilterData(preferences) - var intentFilter = [{ - '$': { - 'android:autoVerify': 'true' - }, - 'action': [{ - '$': { - 'android:name': 'android.intent.action.VIEW' - } - }], - 'category': [{ - '$': { - 'android:name': 'android.intent.category.DEFAULT' - } - }, { - '$': { - 'android:name': 'android.intent.category.BROWSABLE' - } - }], - 'data': data - }] - - return intentFilter - } - - function getAppLinkIntentFilterData (preferences) { - var intentFilterData = [] - - if (preferences.linkDomain.indexOf('app.link') !== -1) { - // app.link - var first = preferences.linkDomain.split('.')[0] - var rest = preferences.linkDomain.split('.').slice(2).join('.') - var alternate = first + '-alternate' + '.' + rest - - intentFilterData.push(getAppLinkIntentFilterDictionary(preferences.linkDomain)) - intentFilterData.push(getAppLinkIntentFilterDictionary(alternate)) - } else if (preferences.linkDomain.indexOf('bnc.lt') !== -1) { - // bnc.lt - if (preferences.androidPrefix == null) { - throw new Error('Branch SDK plugin is missing "android-prefix" in in your config.xml') - } - - intentFilterData.push(getAppLinkIntentFilterDictionary(preferences.linkDomain, preferences.androidPrefix)) - } else { - // custom - intentFilterData.push(getAppLinkIntentFilterDictionary(preferences.linkDomain)) - } - - return intentFilterData - } - - function getAppLinkIntentFilterDictionary (linkDomain, androidPrefix) { - var scheme = 'https' - var output - - if (androidPrefix) { - output = { - '$': { - 'android:host': linkDomain, - 'android:scheme': scheme, - 'android:pathPrefix': androidPrefix - } - } - } else { - output = { - '$': { - 'android:host': linkDomain, - 'android:scheme': scheme - } - } - } - - return output - } })()