└── Android12AndroidManifestAddMissingAndroidExported └── build.gradle /Android12AndroidManifestAddMissingAndroidExported/build.gradle: -------------------------------------------------------------------------------- 1 | /** 2 | * For apps targeting Android 12, if the AndroidManifest.xml file contains , , , or 3 | * components that contain (s), it is required that those components explicitly declare the 4 | * `android:exported` attribute (see https://developer.android.com/about/versions/12/behavior-changes-12#exported). 5 | * This file contains gradle task for adding missing `android:exported` attributes to AndroidManifest.xml files. 6 | * 7 | * 1. copy the content of this file to your `build.gradle` file located in your project's root folder. 8 | * 2. in terminal (cmd), in your project's root folder, execute `./gradlew doAddAndroidExportedIfNecessary` 9 | * 3. if your project still fails to build with the same error about missing the `android:exported` attribute, 10 | * check out the [doAddAndroidExportedForDependencies] task 11 | * 12 | * DISCLAIMER: the gradle task is to help you avoid manually adding the `android:exported` attribute. This comes in 13 | * handy for projects with large AndroidManifest.xml file, projects with multiple AndroidManifest.xml files due to 14 | * multiple flavors and/or multiple app modules. At the end, you should always review changes done on your files 15 | * by this gradle task. Check the following links to make sure the added `android:exported` attributes match your 16 | * use cases: 17 | * - https://developer.android.com/guide/topics/manifest/activity-element#exported 18 | * - https://developer.android.com/guide/topics/manifest/activity-alias-element#exported 19 | * - https://developer.android.com/guide/topics/manifest/service-element#exported 20 | * - https://developer.android.com/guide/topics/manifest/receiver-element#exported 21 | * 22 | */ 23 | 24 | import org.w3c.dom.Element 25 | import org.w3c.dom.Node 26 | 27 | import javax.xml.transform.dom.DOMSource 28 | import javax.xml.transform.stream.StreamResult 29 | import javax.xml.transform.TransformerFactory 30 | import javax.xml.transform.Transformer 31 | 32 | /** 33 | * For apps targeting Android 12, if the AndroidManifest.xml file contains , , , or 34 | * components that contain (s), it is required that those components explicitly declare the 35 | * `android:exported` attribute (see https://developer.android.com/about/versions/12/behavior-changes-12#exported). 36 | * 37 | * This function automatically adds the missing `android:exported` attribute to components that require it. Prior to 38 | * Android 12, for , , and components that have (s), if 39 | * the `android:exported` attribute was not set explicitly, the default value would be `true`. The previous statement 40 | * is based on researching documentation on the `android:exported` attribute: 41 | * - https://developer.android.com/guide/topics/manifest/activity-element#exported 42 | * - https://developer.android.com/guide/topics/manifest/activity-alias-element#exported 43 | * - https://developer.android.com/guide/topics/manifest/service-element#exported 44 | * - https://developer.android.com/guide/topics/manifest/receiver-element#exported 45 | * Therefore, for , , and components that have (s), if 46 | * the `android:exported` attribute is missing, this function adds the attribute with default value `true`. 47 | * For known exceptions, set the value to `false`: 48 | * - firebase messaging service: https://firebase.google.com/docs/cloud-messaging/android/client#manifest 49 | * 50 | * @param manifestFile the AndroidManifest.xml file to be investigated 51 | */ 52 | def addAndroidExportedIfNecessary(File manifestFile) { 53 | def manifestAltered = false 54 | def reader = manifestFile.newReader() 55 | def document = groovy.xml.DOMBuilder.parse(reader) 56 | def application = document.getElementsByTagName("application").item(0) 57 | if (application != null) { 58 | println "Searching for activities, services and receivers with intent filters..." 59 | application.childNodes.each { child -> 60 | def childNodeName = child.nodeName 61 | if (childNodeName == "activity" || childNodeName == "activity-alias" || 62 | childNodeName == "service" || childNodeName == "receiver") { 63 | def attributes = child.getAttributes() 64 | if (attributes.getNamedItem("android:exported") == null) { 65 | def intentFilters = child.childNodes.findAll { 66 | it.nodeName == "intent-filter" 67 | } 68 | if (intentFilters.size() > 0) { 69 | println "found ${childNodeName} ${attributes.getNamedItem("android:name").nodeValue} " + 70 | "with intent filters but without android:exported attribute" 71 | 72 | def exportedAttrAdded = false 73 | for (def i = 0; i < intentFilters.size(); i++) { 74 | def intentFilter = intentFilters[i] 75 | def actions = intentFilter.childNodes.findAll { 76 | it.nodeName == "action" 77 | } 78 | for (def j = 0; j < actions.size(); j++) { 79 | def action = actions[j] 80 | def actionName = action.getAttributes().getNamedItem("android:name").nodeValue 81 | if (actionName == "com.google.firebase.MESSAGING_EVENT") { 82 | println "adding exported=false to ${attributes.getNamedItem("android:name")}..." 83 | ((Element) child).setAttribute("android:exported", "false") 84 | manifestAltered = true 85 | exportedAttrAdded = true 86 | } 87 | } 88 | } 89 | if (!exportedAttrAdded) { 90 | println "adding exported=true to ${attributes.getNamedItem("android:name")}..." 91 | ((Element) child).setAttribute("android:exported", "true") 92 | manifestAltered = true 93 | } 94 | } 95 | } 96 | } 97 | } 98 | } 99 | if (manifestAltered) { 100 | document.setXmlStandalone(true) 101 | Transformer transformer = TransformerFactory.newInstance().newTransformer() 102 | DOMSource source = new DOMSource(document) 103 | FileWriter writer = new FileWriter(manifestFile) 104 | StreamResult result = new StreamResult(writer) 105 | transformer.transform(source, result) 106 | println "Done adding missing android:exported attributes to your AndroidManifest.xml. You may want to" + 107 | "additionally prettify it in Android Studio using [command + option + L](mac) or [CTRL+ALT+L](windows)." 108 | } else { 109 | println "Hooray, your AndroidManifest.xml did not need any change." 110 | } 111 | } 112 | 113 | /** 114 | * Given an AndroidManifest.xml file, extract components with missing `android:exported` attribute, also add that 115 | * attribute to those components. 116 | */ 117 | def getMissingAndroidExportedComponents(File manifestFile) { 118 | List nodesFromDependencies = new ArrayList<>() 119 | def reader = manifestFile.newReader() 120 | def document = groovy.xml.DOMBuilder.parse(reader) 121 | def application = document.getElementsByTagName("application").item(0) 122 | if (application != null) { 123 | println "Searching for activities, services and receivers with intent filters..." 124 | application.childNodes.each { child -> 125 | def childNodeName = child.nodeName 126 | if (childNodeName == "activity" || childNodeName == "activity-alias" || 127 | childNodeName == "service" || childNodeName == "receiver") { 128 | def attributes = child.getAttributes() 129 | if (attributes.getNamedItem("android:exported") == null) { 130 | def intentFilters = child.childNodes.findAll { 131 | it.nodeName == "intent-filter" 132 | } 133 | if (intentFilters.size() > 0) { 134 | println "found ${childNodeName} ${attributes.getNamedItem("android:name").nodeValue} " + 135 | "with intent filters but without android:exported attribute" 136 | 137 | def exportedAttrAdded = false 138 | for (def i = 0; i < intentFilters.size(); i++) { 139 | def intentFilter = intentFilters[i] 140 | def actions = intentFilter.childNodes.findAll { 141 | it.nodeName == "action" 142 | } 143 | for (def j = 0; j < actions.size(); j++) { 144 | def action = actions[j] 145 | def actionName = action.getAttributes().getNamedItem("android:name").nodeValue 146 | if (actionName == "com.google.firebase.MESSAGING_EVENT") { 147 | println "adding exported=false to ${attributes.getNamedItem("android:name")}..." 148 | ((Element) child).setAttribute("android:exported", "false") 149 | exportedAttrAdded = true 150 | } 151 | } 152 | } 153 | if (!exportedAttrAdded) { 154 | println "adding exported=true to ${attributes.getNamedItem("android:name")}..." 155 | ((Element) child).setAttribute("android:exported", "true") 156 | } 157 | nodesFromDependencies.add(child) 158 | } 159 | } 160 | } 161 | } 162 | } 163 | return nodesFromDependencies 164 | } 165 | 166 | /** 167 | * Add [components] to the given an AndroidManifest.xml file's component 168 | */ 169 | def addManifestFileComponents(File manifestFile, List components) { 170 | def reader = manifestFile.newReader() 171 | def document = groovy.xml.DOMBuilder.parse(reader) 172 | def application = document.getElementsByTagName("application").item(0) 173 | if (application != null) { 174 | println "Adding missing components with android:exported attribute to ${manifestFile.absolutePath} ..." 175 | components.each { node -> 176 | Node importedNode = document.importNode(node, true) 177 | application.appendChild(importedNode) 178 | } 179 | } 180 | if (components.size() > 0) { 181 | document.setXmlStandalone(true) 182 | Transformer transformer = TransformerFactory.newInstance().newTransformer() 183 | DOMSource source = new DOMSource(document) 184 | FileWriter writer = new FileWriter(manifestFile) 185 | StreamResult result = new StreamResult(writer) 186 | transformer.transform(source, result) 187 | println "Added missing app-dependencies components with android:exported attributes to your " + 188 | "AndroidManifest.xml.You may want to additionally prettify it in Android Studio using " + 189 | "[command + option + L](mac) or [CTRL+ALT+L](windows)." 190 | } 191 | println "----" 192 | } 193 | 194 | task doAddAndroidExportedIfNecessary { 195 | doLast { 196 | def root = new File(project.rootDir, "") 197 | if (root.isDirectory()) { 198 | def children = root.listFiles() 199 | for (def i = 0; i < children.size(); i++) { 200 | File child = children[i] 201 | if (child.isDirectory()) { 202 | File srcDirectory = new File(child, "src") 203 | if (srcDirectory.exists() && srcDirectory.isDirectory()) { 204 | def srcChildren = srcDirectory.listFiles() 205 | for (def j = 0; j < srcChildren.size(); j++) { 206 | File manifestFile = new File(srcChildren[j], "AndroidManifest.xml") 207 | if (manifestFile.exists() && manifestFile.isFile()) { 208 | println "found manifest file: ${manifestFile.absolutePath}" 209 | addAndroidExportedIfNecessary(manifestFile) 210 | println "-----" 211 | } 212 | } 213 | } 214 | } 215 | } 216 | } 217 | } 218 | } 219 | 220 | /** 221 | * If your project has dependency on libraries that haven't updated their AndroidManifest.xml files yet to conform to 222 | * the Android 12 requirement, your app may still fail to build due to missing `android:exported` attributes in those 223 | * libraries' AndroidManifest.xml files, even after running the [doAddAndroidExportedIfNecessary] task. This task 224 | * extracts the components that are missing the `android:exported` attribute from the merged manifest, which includes 225 | * components from imported libraries, then adds the components to the project's AndroidManifest.xml files that contains 226 | * component. The added components should override their declaration in the libraries' manifest files. 227 | * As we cannot modify the libraries' manifest files, this should be an acceptable workaround. 228 | * 229 | * NOTE: always run [doAddAndroidExportedIfNecessary] first before running this task, in order to avoid adding duplicate 230 | * components to the project's AndroidManifest.xml files. After [doAddAndroidExportedIfNecessary] finishes, rebuild your 231 | * project, otherwise the merged manifest won't be created. Only after those steps, execute this task. 232 | * 233 | * NOTE: This task assumes certain structure of the path to the merged manifest, which is created after project 234 | * build. The path structure may be dependent on the gradle version. This task was tested with gradle-6.8 and 235 | * Android Studio Arctic Fox. 236 | * 237 | * NOTE: If your project already targets Android 12 and still contains libraries with missing `android:exported` 238 | * attributes for required components in their AndroidManifest.xml files, your build will fail and the merged manifest 239 | * won't be created. Therefore, call this task before you target Android 12; or: 240 | * - temporarily downgrade the targetSdkVersion (and compileSDKVersion) to 30 241 | * - run [doAddAndroidExportedIfNecessary] task 242 | * - rebuild your project (to build the merged manifest) 243 | * - run this task 244 | * - set the targetSdkVersion back to target Android 12 245 | */ 246 | task doAddAndroidExportedForDependencies { 247 | doLast { 248 | List missingComponents = new ArrayList<>() 249 | def root = new File(project.rootDir, "") 250 | if (root.isDirectory()) { 251 | def children = root.listFiles() 252 | for (def i = 0; i < children.size(); i++) { 253 | File child = children[i] 254 | if (child.isDirectory()) { 255 | File mergedManifestsDirectory = new File(child, "build/intermediates/merged_manifests") 256 | if (mergedManifestsDirectory.exists() && mergedManifestsDirectory.isDirectory()) { 257 | def manifestFiles = mergedManifestsDirectory.listFiles().findAll { directoryChild -> 258 | directoryChild.isDirectory() && 259 | (new File(directoryChild, "AndroidManifest.xml")).exists() 260 | }.stream().map { directoryWithManifest -> 261 | new File(directoryWithManifest, "AndroidManifest.xml") 262 | }.toArray() 263 | 264 | if (manifestFiles.size() > 0) { 265 | File mergedManifest = manifestFiles[0] 266 | if (mergedManifest.exists() && mergedManifest.isFile()) { 267 | missingComponents = getMissingAndroidExportedComponents(mergedManifest) 268 | 269 | if (missingComponents.size() > 0) { 270 | File srcDirectory = new File(child, "src") 271 | if (srcDirectory.exists() && srcDirectory.isDirectory()) { 272 | def srcChildren = srcDirectory.listFiles() 273 | for (def j = 0; j < srcChildren.size(); j++) { 274 | File manifestFile = new File(srcChildren[j], "AndroidManifest.xml") 275 | if (manifestFile.exists() && manifestFile.isFile()) { 276 | addManifestFileComponents(manifestFile, missingComponents) 277 | } 278 | } 279 | } 280 | } 281 | } 282 | } 283 | } 284 | } 285 | } 286 | } 287 | } 288 | } --------------------------------------------------------------------------------