Introducing app using Cordova 12 which requires target SDK Android 13 / API 33. My app depends on the following plugins (among many others)...
cordova-plugin-camera
cordova-plugin-media-capture
Both plugins insert permissions in AndroidManifest.xml. After upgrading to Cordova 12 and setting targetSdk to 33, the build fails trying to merge permissions...
> Task :app:processReleaseMainManifest FAILED
/Users/jmelvin/dev/sizzlescene/repos/mobile/platforms/android/app/src/main/AndroidManifest.xml:47:5-108 Error:
Element uses-permission#android.permission.WRITE_EXTERNAL_STORAGE at AndroidManifest.xml:47:5-108 duplicated with element declared at AndroidManifest.xml:26:5-81
Here are the properties inserted by the cordova-plugin-camera plugin...
11a12,14
> <provider android:authorities="${applicationId}.cordova.plugin.camera.provider" android:exported="false" android:grantUriPermissions="true" android:name="org.apache.cordova.camera.FileProvider">
> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/camera_provider_paths" />
> </provider>
22a26,41
> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
> <queries>
> <intent>
> <action android:name="android.media.action.IMAGE_CAPTURE" />
> </intent>
> <intent>
> <action android:name="android.intent.action.GET_CONTENT" />
> </intent>
> <intent>
> <action android:name="android.intent.action.PICK" />
> </intent>
> <intent>
> <action android:name="com.android.camera.action.CROP" />
> <data android:mimeType="image/*" android:scheme="content" />
> </intent>
> </queries>
Here are the properties added by cordova-plugin-media-capture plugin...
22a23,28
> <uses-permission android:name="android.permission.RECORD_AUDIO" />
> <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
> <uses-permission android:maxSdkVersion="32" android:name="android.permission.READ_EXTERNAL_STORAGE" />
> <uses-permission android:maxSdkVersion="32" android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
And lastly, when both plugins are included in the build...
11a12,14
> <provider android:authorities="${applicationId}.cordova.plugin.camera.provider" android:exported="false" android:grantUriPermissions="true" android:name="org.apache.cordova.camera.FileProvider">
> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/camera_provider_paths" />
> </provider>
22a26,47
> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
> <queries>
> <intent>
> <action android:name="android.media.action.IMAGE_CAPTURE" />
> </intent>
> <intent>
> <action android:name="android.intent.action.GET_CONTENT" />
> </intent>
> <intent>
> <action android:name="android.intent.action.PICK" />
> </intent>
> <intent>
> <action android:name="com.android.camera.action.CROP" />
> <data android:mimeType="image/*" android:scheme="content" />
> </intent>
> </queries>
> <uses-permission android:name="android.permission.RECORD_AUDIO" />
> <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
> <uses-permission android:maxSdkVersion="32" android:name="android.permission.READ_EXTERNAL_STORAGE" />
> <uses-permission android:maxSdkVersion="32" android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
The permission request for WRITE_EXTERNAL_STORAGE are not exactly the same. The media-capture comes with an SDK qualifier...
camera: android:name="android.permission.WRITE_EXTERNAL_STORAGE"
capture: android:maxSdkVersion="32" android:name="android.permission.WRITE_EXTERNAL_STORAGE"
Manually removing the capture plugin entry does allow the build to complete successfully. However, I'm not sure the result is functionally correct.
Question: is this a bug in the manifest merge code or are there workarounds to circumvent the collision and remain functionally the same?
Question: is this a bug in the manifest merge code or are there workarounds to circumvent the collision and remain functionally the same?
It's not a bug, normally when authoring native projects, if two libraries attempt to declare the same permission with different configurations, it will result similar error.
What is a bug is the fact Cordova's <edit-config>
/<config-file>
directives will result in conflicts and/or duplicate directives in the end-result, which has been a bug for several years.
A hook however can be used to correct the project after_prepare
Add stripExtraWriteExternalStoragePerm.js
file in your hooks/
directory with the script:
const FS = require('fs');
const Path = require('path');
let path = Path.resolve('platforms/android/app/src/main/AndroidManifest.xml');
let manifest = FS.readFileSync(path, {
encoding: 'utf-8'
});
// Strips ALL occurrences of <uses-permission android:name="androoid.permission.WRITE_EXTERNAL_STORAGE" />
// If you have several conflicts (of different maxSDKVersion, or in different formats) then the regex
// may need to be adjusted, or repeated for each format.
manifest = manifest.replace(/^(\s)+<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" \/>$/gm, '');
FS.writeFileSync(path, manifest);
In your config.xml
add:
<widget ...>
...
<platform name="android">
...
<hook type="after_prepare" src="hooks/stripExtraWriteExternalStoragePerm.js" />
</platform>
</widget>
If you already have a <platform>
block for android, then you should add it to your existing <platform>
block.
As the JS comment states, the conflict may occur with any combination of plugins, depending on how they declare WRITE_EXTERNAL_STORAGE
(e.g. with or without the maxSDKVersion
attribute, or with a different maxSDKVersion
attribute specified). If that's the case, you may need to add additional manifest = manifest.replace(...)
lines for your project.
I originally wrote the hook script for work and so the code is released with the copyright of Total Pave Inc. licensed under the Apache License: https://gist.github.com/breautek/bd157b8598f9a816f2ec0d45e3d932c8