Problems compiling to Android

Greetings,

I am having problems compiling to Android all of a sudden and have not been able to identify the reason.

The error Volt is showing is:

UserError: FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:processDebugMainManifest'.
> Manifest merger failed with multiple errors, see logs
See log for full details. 

My settings are:

    <access origin="*" />
    <allow-navigation href="*" />
    <allow-intent href="http://*/*" />
    <allow-intent href="https://*/*" />
    <allow-intent href="tel:*" />
    <allow-intent href="sms:*" />
    <allow-intent href="mailto:*" />
    <allow-intent href="geo:*" />
    <preference name="AndroidXEnabled" value="true" />
    <preference name="android-targetSdkVersion" value="34" />
    <engine name="android" spec="13.0.0" />
    <platform name="android">
        <allow-intent href="market:*" />
        <config-file parent="/manifest" target="AndroidManifest.xml">
            <uses-permission android:name="android.permission.RECORD_AUDIO" />
        </config-file>
    </platform>
    <platform name="ios">
        <edit-config file="*-Info.plist" mode="merge" target="NSLocationWhenInUseUsageDescription">
            <string>...</string>
        </edit-config>
        <edit-config file="*-Info.plist" mode="merge" target="NSCameraUsageDescription">
            <string>...</string>
        </edit-config>
        <edit-config file="*-Info.plist" mode="merge" target="NSPhotoLibraryUsageDescription">
            <string>...</string>
        </edit-config>
    </platform>
    <preference name="SplashScreenDelay" value="2000" />
    <preference name="AutoHideSplashScreen" value="true" />
    <preference name="AndroidWindowSplashScreenAnimatedIcon" value="resources/splashTemplate.png" />
    <preference name="AndroidWindowSplashScreenBackground" value="#FFFFFF" />
    <plugin name="cordova-plugin-file" spec="^8.0.0" />
    <plugin name="cordova-plugin-x-socialsharing-android12" spec="^6.0.5" />
    <plugin name="cordova-plugin-geolocation" spec="^5.0.0" />
    <plugin name="cordova-plugin-network-information" spec="^3.0.0" />
    <plugin name="cordova-plugin-camera" spec="^7.0.0" />
    <plugin name="cordova-plugin-device" spec="^3.0.0" />
    <plugin name="cordova-ios-plugin-no-export-compliance" spec="^0.0.6" />
    <plugin name="cordova-plugin-speechrecognition" spec="^1.2.0" />
    <plugin name="cordova-plugin-media" spec="^7.0.0" />

The relevant part for the full log error is this:

> Task :app:processDebugMainManifest FAILED
/platforms/android/app/src/main/AndroidManifest.xml:48:5-108 Error:
	Element uses-permission#android.permission.WRITE_EXTERNAL_STORAGE at AndroidManifest.xml:48:5-108 duplicated with element declared at AndroidManifest.xml:43:5-81
/platforms/android/app/src/main/AndroidManifest.xml Error:
	Validation failed, exiting

See https://developer.android.com/r/studio-ui/build/manifest-merger for more information about the manifest merger.


FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:processDebugMainManifest'.
> Manifest merger failed with multiple errors, see logs

* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.
> Get more help at https://help.gradle.org.

BUILD FAILED in 16s
25 actionable tasks: 25 executed
Command failed with exit code 1: /platforms/android/tools/gradlew cdvBuildDebug

2024-07-17 21:26:35,891 - FAILURE: Build failed with an exception.

I appreciate your help.

You’ve got two plugins, each adding WRITE_EXTERNAL_STORAGE - one is definitely cordova-plugin-file. I would review the plugin.xml for other plugins to see which is adding the extra one.

1 Like

Indeed, as @artlogic mentions, there are two plugins that declare the android.permission.WRITE_EXTERNAL_STORAGE permission and they do it with different attributes (that is why it is duplicated, one declares maxSdk and the other does not. They are: cordova-plugin-x-socialsharing-android12 and cordova-plugin-camera).

Since the way plugin.xml files are declared is out of my hands (as far as I know), I was able to solve the problem by making use of hooks in “after_prepare” phase, where the AndroidManifest.xml file has already been created.

I added the hook from this m (at least in Ubuntu Ubuntu 20.04.6 LTS it doesn’t work if put inside platform, so I put it directly inside widget):

config.xml

<hook type="after_prepare" src="scripts/android/addRemovePermissions.js" />

addRemovePermissions.js

  • Read the content of AndroidManifest.xml.
  • Define a list of permissions as they would be named inside android:name to remove (removes all their occurrences, regardless of their attributes, is what I need in my case).
  • Define a list of permissions as they would be named inside android:name to add (since I need to restore the required permission that is removed in the first instance).
  • Using regexp remove the permissions in from the string representing the contents of AndroidManifest.xml.
  • It concatenates a string with the permissions to be added.
  • The addition of permissions is done at the end of the content, so it is simply put in front of .
  • AndroidManifest.xml is overwritten.
const fs = require('fs')
const path = require('path')

module.exports = function (ctx) {

	let pathManifest = path.resolve('platforms/android/app/src/main/AndroidManifest.xml')
	let manifest = fs.readFileSync(pathManifest, {
		encoding: 'utf-8'
	})
	let permissionsToRemove = [
		"android.permission.WRITE_EXTERNAL_STORAGE",
	]
	let permissionsToAdd = [
		"android.permission.WRITE_EXTERNAL_STORAGE",
	]
	let permissionsToAddStr = ""

	for (const permission of permissionsToRemove) {
		const regexp = new RegExp(`(\r\n|\n|\r){0,}^(\\t|\\s)*\<uses-permission.*android:name="` + permission + `".*\/\>$`, 'gm')
		manifest = manifest.replace(regexp, '')
	}

	for (const permission of permissionsToAdd) {
		permissionsToAddStr += `    <uses-permission android:name="${permission}" />\n`
	}
	manifest = manifest.replace("</manifest>", `${permissionsToAddStr}</manifest>`)

	return new Promise(function (resolve) {
		fs.writeFileSync(pathManifest, manifest)
		resolve()
	})
}

This solves the problem, I hope the solution is useful.