Replace launching activity with flavors - android

Is it possible to replace the activity that gets the intent-filter
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
by configuring flavors?

There are a number of ways to accomplish this thanks to the manifest merger.
The easiest way is to use a placeholder in your manifest and define the appropriate class in your build.gradle.
For example, in your manifest:
<activity android:name="${launchActivityName}">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
And in your build.gradle:
productFlavors {
flavor1 {
manifestPlaceholders = [ launchActivityName:"com.example.MainActivity"]
}
}
You can also include a different manifest file with each flavor.

Yes, just add a manifest file in the folder of the product flavour and
The Manifest of the product flavor is merged on top of the Manifest of the main configuration.
More info here: http://tools.android.com/tech-docs/new-build-system/build-system-concepts

In addition to #Tanis answer, I want to add some parts you should aware of.
productFlavors {
lite {
dimension "default"
applicationId "com.foo.bar"
manifestPlaceholders = [ applicationLabel:"#string/app_name_foo"
, launcherAct: "com.foo.qux.activity.Hello"
, launcherAct2: "com.foo.qux.activity.World"]
}
full {
dimension "default"
applicationId "com.foo.qux"
manifestPlaceholders = [ applicationLabel:"#string/app_name_bar"
, launcherAct: ".activity.World"
, launcherAct2: ".activity.Hello"]
}
}
If let's say, your World.java exists in com.foo.qux.activity folder, then you should use full path com.foo.qux.activity.World instead of .activity.World when put it inside applicationId "com.foo.bar" flavor, or else it will auto prepend to com.foo.bar.activity.World which the file doesn't exist.
Unlike android:label, you can't use something like #string/launcher_class in android:name, or else you will get error: attribute 'android:name' in <activity> tag must be a valid Java class name. when build.
Ensures you don't duplicate the class, e.g. :
<activity
android:name="${launcherAct}"
android:label="${applicationLabel}">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name="${launcherAct2}"
android:label="${applicationLabel}">
</activity>
Don't forgot to change from .activity.World to android:name="${launcherAct2}" or else you end up duplicated activity (i.e. one in AndroidManifest.xml, the other one in manifestPlaceholders).
The Unresolved class red indicator can safely ignored, seems like Android Studio bug:
I make a bug report here.

Related

Android How can i override exported tag in manifest?

I want to upgrade target sdk from 30 to 31. But i'm getting that error :
Manifest merger failed : android:exported needs to be explicitly
specified for . Apps targeting Android 12 and higher are
required to specify an explicit value for android:exported when the
corresponding component has an intent filter defined. See
https://developer.android.com/guide/topics/manifest/activity-element#exported
for details.
When i'm look around in manifest merger. I see one of the third party library which i use in the app didn't add android:exported tag.
<service android:name="com.****.MessagingService" >
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
So here is my question. How can i override this service as exported false like below ?
<service android:exported="false"
android:name="com.****.MessagingService" >
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
Android Studio will merge all manifest files of your project and its dependencies.
The upper level manifests can always take priority and overwrite the values in the dependencies. Check this paragraph in the documentation.
Since the dependencies do not specify the exported value at all, you can overwrite each service in your top level manifest file like this:
<service android:exported="false"
android:name="com.****.MessagingService" />
This will merge the values for the service tag for com.****.MessagingService and apply the exported value.
You can either replace the whole node
<service
tools:node="replace"
android:name=".MessagingService"
android:exported="false">
<intent-filter>
<action android:name="com.google.firebase.MESSAGING_EVENT" />
</intent-filter>
</service>
Or just the attribute you need to replace
<service
tools:replace="exported"
android:name=".MessagingService"
android:exported="false"/>
You can find more information about this topic here

Android Manifest Merger: Warning related to no other declaration present

I checked plethora of similar posts related to this topic but non was able to point out the issue which I am facing.
So basically I have a Module having a Launcher activitywhich I don't want to be the main Launcher by overriding the Main Module's Launcher Activity. So, I used the node: tools:node="remove" to remove that. Example:
Main App's manifest:
...
<activity
android:name="path.ThatOneActivity"
android:screenOrientation="locked"
android:theme="#style/AppCompatTheme.Translucent.NoTitleBar">
<intent-filter>
<action
android:name="android.intent.action.MAIN" />
<category
android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="nextActivity"
android:value="path.ThatOtherActivity" />
</activity>
My Module's manifest:
<activity
android:name="path.ThatOneActivity"
android:screenOrientation="locked"
android:theme="#style/AppCompatTheme.Translucent.NoTitleBar"
tools:node="merge">
<intent-filter tools:node="remove">
<action
android:name="android.intent.action.MAIN"
tools:node="remove" />
<category
android:name="android.intent.category.LAUNCHER"
tools:node="remove" />
</intent-filter>
<meta-data
android:name="nextActivity"
android:value="path.ThatOtherActivity" />
</activity>
So, as you can see, this is actually a mixture of multiple solutions on different threads but it is not working. (Yes, not even individually).
I then checked the logs while building, I found a warning:
category#android.intent.category.LAUNCHER was tagged at AndroidManifest.xml: to remove other declarations but no other declaration present
This is now confusing as I am not sure what it means. As per other threads, I did add the xmlns:tools too but nothing happened:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="my.borning.app">
Update:
Made the changes as per CommonsWare's comment:
<activity android:name="path.ThatOneActivity">
<intent-filter>
<action
android:name="android.intent.action.MAIN"
tools:node="remove" />
<category
android:name="android.intent.category.LAUNCHER"
tools:node="remove" />
</intent-filter>
</activity>
but still getting the Warning:
category#android.intent.category.LAUNCHER was tagged at AndroidManifest.xml: to remove other declarations but no other declaration present
I figured one thing though:
I am working on a module which is used by the main app (which is also not managed by me, my module is a library which I had it wrong before. So this has to do with the Priority! Mine being the library trying to override the Launcher of the Main App which is using my module as a Library. Is there a way to override the Launcher via a library (basically if we can make it's Manifest's Priority higher, which doesn't sound possible)?
It would be helpful if you can guide me to a concrete direction. Let me know if you want more details.
Thank you in advance!!

Manifest merger failed caused by an error with manifestPlaceholders in multimodule project

I have a little issue when trying to execute the androidUnitTest:
"Manifest merger failed: Attribute data#scheme at
manifestMerger8944498509061862801.xml requires a placeholder
substitution but no value for is provided"
In my Android module manifest I have this intent filter:
<activity
android:name="myactivity"
android:configChanges="keyboardHidden|orientation|screenSize|keyboard|navigation"
android:theme="#style/actionBarTheme"
tools:node="replace">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:scheme="${urlScheme}"
android:host="action"/>
</intent-filter>
</activity>
and the manifestPlaceHolders are defined at the Gradle application level in the flavors.
I have tried to set the manifestPlaceHolder also in the module level, in that way the unit test go well, but in this way the intent filter when I run the app stop to working because in the merged file the URL scheme is the one defined in the module level

Multiple app icons being installed when using Flavors and multiple manifest files

I currently have a project that needs to contain two different forms of codebase, legacy and an updated version of the application. I am using Flavors for this, but am running into an issue where two app icons are being installed. The reason is because both the legacy codebase and the updated codebase have their own manifest.xml, and inside the manifest are declarations for identifying the Main launch class and their relative app icon.
<!-- legacy code manifest -->
<activity
android:name="legacy.activity.RegistrationActivity"
android:configChanges="orientation|screenSize"
android:label="#string/app_name"
android:screenOrientation="portrait"
android:theme="#style/CustomAppTheme"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- updated code manifest -->
<activity
android:name="updated.activity.RegistrationActivity"
android:configChanges="orientation|screenSize"
android:label="#string/app_name"
android:screenOrientation="portrait"
android:theme="#style/CustomAppTheme"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
How can I get around this? If I remove the intent-filter from the updated code base, which is the Main codebase, the Flavor will not install two icons. However, I am unable to run the Main codebase because I have not declared an entry point in my Manifest. And conversely, if I remove the intent-filter from the Flavor and keep it in my Main codebase, the Flavor code will not run. The two RegistrationActivity classes are different, just with the same name. The Legacy code does not really share much of the updated codebase. Any suggestions other than separate into different projects?
Some have requested my setup with Flavors in gradle, here is snapshot of it.
productFlavors {
standard {
applicationId 'updated.android.example'
manifestPlaceholders = [package_name: "updated.android.example", primary_lang: "en"]
signingConfig signingConfigs.keystore
}
legacyTest {
applicationId 'legacy.android.example.debug'
manifestPlaceholders = [package_name: "legacy.android.example.debug",
target : "Test", primary_lang: "en"]
signingConfig signingConfigs.keystore
}
legacyProd {
applicationId 'legacy.android.example.prod'
manifestPlaceholders = [package_name: "legacy.android.example.prod",
target : "Prod", primary_lang: "en"]
signingConfig signingConfigs.keystore
}
This line:
<category android:name="android.intent.category.LAUNCHER"/>
says that you have a launching activity. Set them to a default activity setting(this is just "another activity", create a third class that is the launcher-class. This class will automatically redirect to one of the two other activities based on whatever specifications you may have, such as API level or brand. Any specifications you have as to which to launch, set them and the user will not know there is a handling activity.
When you have two launcher activities, they show as two applications. This is probably because the system cannot determine what to redirect to automatically. So there are two depending on what to launch.
How can I get around this? If I remove the intent-filter from the updated code base, which is the Main codebase,
Don't remove it. Change it. E.g.:
<intent-filter>
<action android:name="com.yourpackage.name.CLASSNAME" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
You need separate <activity android:name="legacy.activity.RegistrationActivity" and <activity android:name="updated.activity.RegistrationActivity" you two manifests, one for each flavor.
eg.:
src/main/AndroidManifest.xml (with everything except: *.activity.RegistrationActivity)
src/legacy/AndroidManifest.xml (with legacy.activity.RegistrationActivity)
src/updated/AndroidManifest.xml (with updated.activity.RegistrationActivity)

Gradle: Manifest merge changes the value for the "theme" parameters in the AndroidManifest file:

The problem I have is that if I have in the base Manifest file an Acitivity, say like so:
<activity
android:name=".activities.ActTutorial"
android:label="#string/app_name"
android:theme="#android:style/Theme.Holo.NoActionBar"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
After I merge the manifest files, produce the apk file and open this apk file (using apktool) to look at the AndroidManifest.xml file I see this:
<activity
android:theme="#*android:style/Theme.Holo.NoActionBar"
android:label="#string/app_name"
android:name=".activities.ActTutorial"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
The android:theme value changed from:
"#android:style/Theme.Holo.NoActionBar"
to
"#*android:style/Theme.Holo.NoActionBar"
As you can see there is an asterisk (*) there and this basically result in showing me an activity with ActionBar when in fact I need one without.
Does some one knows why this happens? and this can be fixed?
In the end the asterisk was not my problem for this thing, but I had some problem with the application package names, what helped in this case was to use:
${applicationId}. place holder in the manifest file for different packages of the different flavor of the project.
You can use it like so:
<permission
android:name="${applicationId}.permission.C2D_MESSAGE"
android:protectionLevel="signature"/>
<uses-permission
android:name="${applicationId}.permission.C2D_MESSAGE"/>

Categories

Resources