Skip to content

Instantly share code, notes, and snippets.

@cmelchior
Last active December 25, 2015 13:59
Show Gist options
  • Star 11 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save cmelchior/6988275 to your computer and use it in GitHub Desktop.
Save cmelchior/6988275 to your computer and use it in GitHub Desktop.
Adding support for ".res-auto" in ContentProvider authorities in AndroidManifest.xml. This replaces ".res-auto" with the current package name, making it possible to install multiple build variants on the same devices without getting [INSTALL_FAILED_CONFLICTING_PROVIDER]
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.trifork.example"
android:installLocation="auto"
android:versionName="@string/client_info" >
<!-- ... -->
<application
android:hardwareAccelerated="true"
android:icon="@drawable/icon"
android:label="@string/app_name"
android:theme="@style/Theme.Standard" >
<!-- ... -->
<provider
android:name="path.to.provider.ProviderImpl"
android:authorities=".res-auto.path.to.provider.ProviderImpl"
android:exported="false" >
</provider>
<provider
android:name="path.to.provider.ProviderImpl"
android:authorities="@string/provider_auth"
android:exported="false" >
</provider>
</application>
</manifest>
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:0.6.+'
}
}
apply plugin: 'android'
apply from: './build_extras.gradle'
repositories {
mavenCentral()
}
android {
compileSdkVersion 18
buildToolsVersion "18.0.1"
defaultConfig {
minSdkVersion 10
targetSdkVersion 18
}
}
dependencies {
}
/**
* Version 1.1.
*
* Add support for installing multiple variants of the same app which have a
* content provider. Do this by overriding occurrences of ".res-auto" in
* android:authorities with the current package name (which should be unique)
*
* V1.0 : Initial version
* V1.1 : Support for ".res-auto" in strings added,
* eg. use "<string name="auth">.res-auto.path.to.provider</string>"
*
*/
def overrideProviderAuthority(buildVariant) {
def flavor = buildVariant.productFlavors.get(0).name
def buildType = buildVariant.buildType.name
def pathToManifest = "${buildDir}/manifests/${flavor}/${buildType}/AndroidManifest.xml"
def ns = new groovy.xml.Namespace("http://schemas.android.com/apk/res/android", "android")
def xml = new XmlParser().parse(pathToManifest)
def variantPackageName = xml.@package
// Update all content providers
xml.application.provider.each { provider ->
def newAuthorities = provider.attribute(ns.authorities).replaceAll('.res-auto', variantPackageName)
provider.attributes().put(ns.authorities, newAuthorities)
}
// Save modified AndroidManifest back into build dir
saveXML(pathToManifest, xml)
// Also make sure that all strings with ".res-auto" are expanded automagically
def pathToValues = "${buildDir}/res/all/${flavor}/${buildType}/values/values.xml"
xml = new XmlParser().parse(pathToValues)
xml.findAll{it.name() == 'string'}.each{item ->
if (!item.value().isEmpty() && item.value()[0].startsWith(".res-auto")) {
item.value()[0] = item.value()[0].replace(".res-auto", variantPackageName)
}
}
saveXML(pathToValues, xml)
}
def saveXML(pathToFile, xml) {
def writer = new FileWriter(pathToFile)
def printer = new XmlNodePrinter(new PrintWriter(writer))
printer.preserveWhitespace = true
printer.print(xml)
}
// Post processing of AndroidManifest.xml for supporting provider authorities
// across build variants.
android.applicationVariants.all { variant ->
variant.processManifest.doLast {
overrideProviderAuthority(variant)
}
}
@paour
Copy link

paour commented Jan 17, 2014

Please see my fork for a small fix.

Prevent exceptions for cases like <string name="with_markup"><b>Warning</b>This will crash the build</string>

@influenced
Copy link

The problem I have with this is it works during a rebuild, but if I attempt to deploy via the RUN button in the Android Studio GUI the strings in the values.xml file appear to get re-replaced with the original values (the manifest file is still correctly processed).

I'm executing this script during variant.processResources.doLast as well as processManifest, and I added some logging so I can tell it's still getting executed - but the strings will not have replaced ".res-auto" once it's deployed (If I examine values.xml after a Build->Rebuild Project, it's correct, but if I examine it after I hit Run it has the wrong string values - however, I can see that the script is running in the processResources phase in the gradle console.)

EDIT: this is with Gradle 1.9

@martinmalek
Copy link

This works very well for the AndroidManifest provider authorities substitution. However for each of my three providers classes I declare unique authorities.

private static final String IDENTITY_AUTHORITY = "com.company.android.identityprovider";
private static final String CUSTOMER_AUTHORITY = "com.company.android.customerprovider";
private static final String BLABLA_AUTHORITY = "com.company.android.blablaprovider";

I found out the hard way that these must be named the same as the ones in the AndroidManifest.xml for it to work.

Any ideas on how to best substitute these too?

I tried using BuildConfigLine but failed miserably :)

@influenced
Copy link

OK, I still haven't got the modified value.xml to make it into a deployed build with the latest Android Studio plugin/Gradle, but I have got the process working other than that.

The alternative I'm using is simply to define something like:

public final static String AUTHORITY = BuildConfig.PACKAGE_NAME + ".MyProviderName";

in my provider class, and using the direct form of .res-auto in the manifest:

<provider android:name=".MyProviderName" android:authorities=".res-auto.MyProviderName" />

which works fine. Since the provider I'm specifically using is a SearchSuggestionProvider, I also tried adding a new section to overrideProviderAuthority that processes searchable configuration xml files, but this suffers from the same issue as values.xml - the modified version is getting replaced with the original prior to deployment.

I do not, as yet, have a workaround for that...

@paour
Copy link

paour commented Feb 21, 2014

@influenced, in searchable.xml you can use something like

<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
            android:label="@string/app_name"
            android:hint="@string/search_hint"
            android:searchSuggestAuthority="@string/search_provider"
            android:searchSuggestSelection=" ?"
        >
</searchable>

and add to your strings.xml

    <string name="search_provider">.res-auto.util.LocationSuggestionProvider</string>

But I do have the same issue with the values being overwritten when I use IntelliJ.

@influenced
Copy link

That's what I'd like to do - and what I originally did - but by the time it gets onto the device, it'll have been reset to .res-auto again, so the OS won't be able to find that provider.

@paour
Copy link

paour commented Feb 24, 2014

I spent way too much time on this, but I finally have a version that works. Please check out https://gist.github.com/paour/9189462

@martingg88
Copy link

@influenced: do you manage to get .res-auto replaced when you hit run/debug?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment