diff --git a/BREAKING-CHANGES.txt b/BREAKING-CHANGES.txt index 277a540f47..eae584b0bf 100644 --- a/BREAKING-CHANGES.txt +++ b/BREAKING-CHANGES.txt @@ -45,6 +45,58 @@ rather than using a dedicated scrollbar. The scrollbar is still available though needed. +Change +------ +The previous setting of Android exporter "Custom manifest xml elements" creating +child nodes of element has been replaced by "Custom manifest XML content" +setting that allows to specify the content of the entire manifest instead. +Any previously values of the old setting will be used in the new setting by default, and +they will need changing as mentioned in Workaround. The custom content will be merged +with the content auto-generated by Projucer. Any custom elements or custom attributes +will override the ones set by Projucer. Projucer will also automatically add any +missing and required elements and attributes. + + +Possible Issues +--------------- +If a Projucer project used "Custom manifest xml elements" field, the value will no +longer be compatible with the project generated in the latest Projucer version. The solution +is very simple and quick though, as mentioned in the Workaround section. + + +Workaround +---------- +For any elements previously used, simply embed them explicitly in +elements,for example instead of: + + + + +simply write: + + + + + + + + + +Rationale +--------- +To maintain the high level of flexibility of generated Android projects and to avoid +creating fields in Projucer for every possible future parameter, it is simpler to allow to +set up the required parameters manually. This way it is not only possible to add any custom +elements but it is also possible to override the default attributes assigned by Projucer for +the required elements. For instance, if the default value of element is +not satisfactory because you want a support for x-large screens only, simply set +"Custom manifest XML content" to: + + + + + + Version 5.1.2 ============= diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h index 63a854c1ad..1ff3e7f790 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Android.h @@ -841,8 +841,10 @@ private: props.add (new TextPropertyComponent (androidOtherPermissions.getPropertyAsValue(), "Custom permissions", 2048, false), "A space-separated list of other permission flags that should be added to the manifest."); - props.add (new TextPropertyComponent (androidManifestCustomXmlElements.getPropertyAsValue(), "Custom manifest xml elements", 8192, true), - "You can specify custom XML elements that will be added to AndroidManifest.xml as children of element."); + props.add (new TextPropertyComponent (androidManifestCustomXmlElements.getPropertyAsValue(), "Custom manifest XML content", 8192, true), + "You can specify custom AndroidManifest.xml content overriding the default one generated by Projucer. " + "Projucer will automatically create any missing and required XML elements and attributes " + "and merge them into your custom content."); } //============================================================================== @@ -1366,28 +1368,39 @@ private: //============================================================================== XmlElement* createManifestXML() const { - XmlElement* manifest = new XmlElement ("manifest"); + XmlElement* manifest = XmlDocument::parse (androidManifestCustomXmlElements.get()); - manifest->setAttribute ("xmlns:android", "http://schemas.android.com/apk/res/android"); - manifest->setAttribute ("android:versionCode", androidVersionCode.get()); - manifest->setAttribute ("android:versionName", project.getVersionString()); - manifest->setAttribute ("package", getActivityClassPackage()); + if (manifest == nullptr) + manifest = new XmlElement ("manifest"); + + setAttributeIfNotPresent (*manifest, "xmlns:android", "http://schemas.android.com/apk/res/android"); + setAttributeIfNotPresent (*manifest, "android:versionCode", androidVersionCode.get()); + setAttributeIfNotPresent (*manifest, "android:versionName", project.getVersionString()); + setAttributeIfNotPresent (*manifest, "package", getActivityClassPackage()); if (! isLibrary()) { - XmlElement* screens = manifest->createNewChildElement ("supports-screens"); - screens->setAttribute ("android:smallScreens", "true"); - screens->setAttribute ("android:normalScreens", "true"); - screens->setAttribute ("android:largeScreens", "true"); - screens->setAttribute ("android:anyDensity", "true"); + if (manifest->getChildByName ("supports-screens") == nullptr) + { + XmlElement* screens = manifest->createNewChildElement ("supports-screens"); + screens->setAttribute ("android:smallScreens", "true"); + screens->setAttribute ("android:normalScreens", "true"); + screens->setAttribute ("android:largeScreens", "true"); + screens->setAttribute ("android:anyDensity", "true"); + } } - XmlElement* sdk = manifest->createNewChildElement ("uses-sdk"); - sdk->setAttribute ("android:minSdkVersion", androidMinimumSDK.get()); - sdk->setAttribute ("android:targetSdkVersion", androidMinimumSDK.get()); + auto* sdk = getOrCreateChildWithName (*manifest, "uses-sdk"); + setAttributeIfNotPresent (*sdk, "android:minSdkVersion", androidMinimumSDK.get()); + setAttributeIfNotPresent (*sdk, "android:targetSdkVersion", androidMinimumSDK.get()); { - const StringArray permissions (getPermissionsRequired()); + StringArray permissions (getPermissionsRequired()); + + forEachXmlChildElementWithTagName (*manifest, child, "uses-permission") + { + permissions.removeString (child->getStringAttribute ("android:name"), false); + } for (int i = permissions.size(); --i >= 0;) manifest->createNewChildElement ("uses-permission")->setAttribute ("android:name", permissions[i]); @@ -1395,19 +1408,33 @@ private: if (project.getModules().isModuleEnabled ("juce_opengl")) { - XmlElement* feature = manifest->createNewChildElement ("uses-feature"); - feature->setAttribute ("android:glEsVersion", (androidMinimumSDK.get().getIntValue() >= 18 ? "0x00030000" : "0x00020000")); - feature->setAttribute ("android:required", "true"); + XmlElement* glVersion = nullptr; + + forEachXmlChildElementWithTagName (*manifest, child, "uses-feature") + { + if (child->getStringAttribute ("android:glEsVersion").isNotEmpty()) + { + glVersion = child; + break; + } + } + + if (glVersion == nullptr) + glVersion = manifest->createNewChildElement ("uses-feature"); + + setAttributeIfNotPresent (*glVersion, "android:glEsVersion", (androidMinimumSDK.get().getIntValue() >= 18 ? "0x00030000" : "0x00020000")); + setAttributeIfNotPresent (*glVersion, "android:required", "true"); } if (! isLibrary()) { - XmlElement* app = manifest->createNewChildElement ("application"); - app->setAttribute ("android:label", "@string/app_name"); + auto* app = getOrCreateChildWithName (*manifest, "application"); + setAttributeIfNotPresent (*app, "android:label", "@string/app_name"); if (androidTheme.get().isNotEmpty()) - app->setAttribute ("android:theme", androidTheme.get()); + setAttributeIfNotPresent (*app, "android:theme", androidTheme.get()); + if (! app->hasAttribute ("android:icon")) { ScopedPointer bigIcon (getBigIcon()), smallIcon (getSmallIcon()); @@ -1417,29 +1444,66 @@ private: if (androidMinimumSDK.get().getIntValue() >= 11) app->setAttribute ("android:hardwareAccelerated", "false"); // (using the 2D acceleration slows down openGL) + else + app->removeAttribute ("android:hardwareAccelerated"); + + auto* act = getOrCreateChildWithName (*app, "activity"); + + setAttributeIfNotPresent (*act, "android:name", getActivitySubClassName()); + setAttributeIfNotPresent (*act, "android:label", "@string/app_name"); + + if (! act->hasAttribute ("android:configChanges")) + { + String configChanges ("keyboardHidden|orientation"); + if (androidMinimumSDK.get().getIntValue() >= 13) + configChanges += "|screenSize"; + + act->setAttribute ("android:configChanges", configChanges); + } + else + { + auto configChanges = act->getStringAttribute ("android:configChanges"); - XmlElement* act = app->createNewChildElement ("activity"); - act->setAttribute ("android:name", getActivitySubClassName()); - act->setAttribute ("android:label", "@string/app_name"); + if (androidMinimumSDK.get().getIntValue() < 13 && configChanges.contains ("screenSize")) + { + configChanges = configChanges.replace ("|screenSize", "") + .replace ("screenSize|", "") + .replace ("screenSize", ""); + + act->setAttribute ("android:configChanges", configChanges); + } + } - String configChanges ("keyboardHidden|orientation"); - if (androidMinimumSDK.get().getIntValue() >= 13) - configChanges += "|screenSize"; + setAttributeIfNotPresent (*act, "android:screenOrientation", androidScreenOrientation.get()); - act->setAttribute ("android:configChanges", configChanges); - act->setAttribute ("android:screenOrientation", androidScreenOrientation.get()); + auto* intent = getOrCreateChildWithName (*act, "intent-filter"); - XmlElement* intent = act->createNewChildElement ("intent-filter"); - intent->createNewChildElement ("action")->setAttribute ("android:name", "android.intent.action.MAIN"); - intent->createNewChildElement ("category")->setAttribute ("android:name", "android.intent.category.LAUNCHER"); + auto* action = getOrCreateChildWithName (*intent, "action"); + setAttributeIfNotPresent (*action, "android:name", "android.intent.action.MAIN"); - for (XmlElement* e = XmlDocument::parse (androidManifestCustomXmlElements.get()); e != nullptr; e = e->getNextElement()) - app->addChildElement (e); + auto* category = getOrCreateChildWithName (*intent, "category"); + setAttributeIfNotPresent (*category, "android:name", "android.intent.category.LAUNCHER"); } return manifest; } + static XmlElement* getOrCreateChildWithName (XmlElement& element, const String& name) + { + auto* child = element.getChildByName (name); + + if (child == nullptr) + child = element.createNewChildElement (name); + + return child; + } + + static void setAttributeIfNotPresent (XmlElement& element, const Identifier& attribute, const String& value) + { + if (! element.hasAttribute (attribute.toString())) + element.setAttribute (attribute, value); + } + StringArray getPermissionsRequired() const { StringArray s;