@@ -404,12 +404,8 @@ | |||||
file="Source/Project/jucer_TreeItemTypes.h"/> | file="Source/Project/jucer_TreeItemTypes.h"/> | ||||
</GROUP> | </GROUP> | ||||
<GROUP id="{28174A5F-D7AD-7240-6104-135D5D2DDB3C}" name="Project Saving"> | <GROUP id="{28174A5F-D7AD-7240-6104-135D5D2DDB3C}" name="Project Saving"> | ||||
<FILE id="rJVyvQ" name="jucer_ProjectExport_AndroidAnt.h" compile="0" | |||||
resource="0" file="Source/Project Saving/jucer_ProjectExport_AndroidAnt.h"/> | |||||
<FILE id="nB4AaL" name="jucer_ProjectExport_AndroidBase.h" compile="0" | |||||
resource="0" file="Source/Project Saving/jucer_ProjectExport_AndroidBase.h"/> | |||||
<FILE id="eDGR9w" name="jucer_ProjectExport_AndroidStudio.h" compile="0" | |||||
resource="0" file="Source/Project Saving/jucer_ProjectExport_AndroidStudio.h"/> | |||||
<FILE id="eDGR9w" name="jucer_ProjectExport_Android.h" compile="0" | |||||
resource="0" file="Source/Project Saving/jucer_ProjectExport_Android.h"/> | |||||
<FILE id="lYuOMq" name="jucer_ProjectExport_CodeBlocks.h" compile="0" | <FILE id="lYuOMq" name="jucer_ProjectExport_CodeBlocks.h" compile="0" | ||||
resource="0" file="Source/Project Saving/jucer_ProjectExport_CodeBlocks.h"/> | resource="0" file="Source/Project Saving/jucer_ProjectExport_CodeBlocks.h"/> | ||||
<FILE id="lv4d6B" name="jucer_ProjectExport_Make.h" compile="0" resource="0" | <FILE id="lv4d6B" name="jucer_ProjectExport_Make.h" compile="0" resource="0" | ||||
@@ -1,475 +0,0 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2015 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
class AndroidAntProjectExporter : public AndroidProjectExporterBase | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
bool canLaunchProject() override { return false; } | |||||
bool launchProject() override { return false; } | |||||
bool isAndroid() const override { return true; } | |||||
bool usesMMFiles() const override { return false; } | |||||
bool canCopeWithDuplicateFiles() override { return false; } | |||||
bool supportsUserDefinedConfigurations() const override { return true; } | |||||
bool isAndroidStudio() const override { return false; } | |||||
bool isAndroidAnt() const override { return true; } | |||||
bool supportsTargetType (ProjectType::Target::Type type) const override | |||||
{ | |||||
switch (type) | |||||
{ | |||||
case ProjectType::Target::GUIApp: | |||||
case ProjectType::Target::StaticLibrary: | |||||
return true; | |||||
default: | |||||
break; | |||||
} | |||||
return false; | |||||
} | |||||
//============================================================================== | |||||
static const char* getName() { return "Android Ant Project"; } | |||||
static const char* getValueTreeTypeName() { return "ANDROID"; } | |||||
//============================================================================== | |||||
Value getNDKToolchainVersionValue() { return getSetting (Ids::toolset); } | |||||
String getNDKToolchainVersionString() const { return settings [Ids::toolset]; } | |||||
Value getStaticLibrariesValue() { return getSetting (Ids::androidStaticLibraries); } | |||||
String getStaticLibrariesString() const { return settings [Ids::androidStaticLibraries]; } | |||||
Value getSharedLibrariesValue() { return getSetting (Ids::androidSharedLibraries); } | |||||
String getSharedLibrariesString() const { return settings [Ids::androidSharedLibraries]; } | |||||
//============================================================================== | |||||
static AndroidAntProjectExporter* createForSettings (Project& project, const ValueTree& settings) | |||||
{ | |||||
if (settings.hasType (getValueTreeTypeName())) | |||||
return new AndroidAntProjectExporter (project, settings); | |||||
return nullptr; | |||||
} | |||||
//============================================================================== | |||||
AndroidAntProjectExporter (Project& p, const ValueTree& t) | |||||
: AndroidProjectExporterBase (p, t) | |||||
{ | |||||
name = getName(); | |||||
if (getTargetLocationString().isEmpty()) | |||||
getTargetLocationValue() = getDefaultBuildsRootFolder() + "Android"; | |||||
} | |||||
//============================================================================== | |||||
void createToolchainExporterProperties (PropertyListBuilder& props) override | |||||
{ | |||||
props.add (new TextPropertyComponent (getNDKToolchainVersionValue(), "NDK Toolchain version", 32, false), | |||||
"The variable NDK_TOOLCHAIN_VERSION in Application.mk - leave blank for a default value"); | |||||
} | |||||
void createLibraryModuleExporterProperties (PropertyListBuilder& props) override | |||||
{ | |||||
props.add (new TextPropertyComponent (getStaticLibrariesValue(), "Import static library modules", 8192, true), | |||||
"Comma or whitespace delimited list of static libraries (.a) defined in NDK_MODULE_PATH."); | |||||
props.add (new TextPropertyComponent (getSharedLibrariesValue(), "Import shared library modules", 8192, true), | |||||
"Comma or whitespace delimited list of shared libraries (.so) defined in NDK_MODULE_PATH."); | |||||
} | |||||
//============================================================================== | |||||
void create (const OwnedArray<LibraryModule>& modules) const override | |||||
{ | |||||
AndroidProjectExporterBase::create (modules); | |||||
const File target (getTargetFolder()); | |||||
const File jniFolder (target.getChildFile ("jni")); | |||||
createDirectoryOrThrow (jniFolder); | |||||
createDirectoryOrThrow (target.getChildFile ("res").getChildFile ("values")); | |||||
createDirectoryOrThrow (target.getChildFile ("libs")); | |||||
createDirectoryOrThrow (target.getChildFile ("bin")); | |||||
{ | |||||
ScopedPointer<XmlElement> manifest (createManifestXML()); | |||||
writeXmlOrThrow (*manifest, target.getChildFile ("AndroidManifest.xml"), "utf-8", 100, true); | |||||
} | |||||
writeApplicationMk (jniFolder.getChildFile ("Application.mk")); | |||||
writeAndroidMk (jniFolder.getChildFile ("Android.mk")); | |||||
{ | |||||
ScopedPointer<XmlElement> antBuildXml (createAntBuildXML()); | |||||
writeXmlOrThrow (*antBuildXml, target.getChildFile ("build.xml"), "UTF-8", 100); | |||||
} | |||||
writeProjectPropertiesFile (target.getChildFile ("project.properties")); | |||||
writeLocalPropertiesFile (target.getChildFile ("local.properties")); | |||||
writeStringsFile (target.getChildFile ("res/values/strings.xml")); | |||||
writeIcons (target.getChildFile ("res")); | |||||
} | |||||
//============================================================================== | |||||
class AndroidBuildConfiguration : public BuildConfiguration | |||||
{ | |||||
public: | |||||
AndroidBuildConfiguration (Project& p, const ValueTree& settings, const ProjectExporter& e) | |||||
: BuildConfiguration (p, settings, e) | |||||
{ | |||||
if (getArchitectures().isEmpty()) | |||||
{ | |||||
if (isDebug()) | |||||
getArchitecturesValue() = "armeabi x86"; | |||||
else | |||||
getArchitecturesValue() = "armeabi armeabi-v7a x86"; | |||||
} | |||||
} | |||||
Value getArchitecturesValue() { return getValue (Ids::androidArchitectures); } | |||||
String getArchitectures() const { return config [Ids::androidArchitectures]; } | |||||
var getDefaultOptimisationLevel() const override { return var ((int) (isDebug() ? gccO0 : gccO3)); } | |||||
void createConfigProperties (PropertyListBuilder& props) override | |||||
{ | |||||
addGCCOptimisationProperty (props); | |||||
props.add (new TextPropertyComponent (getArchitecturesValue(), "Architectures", 256, false), | |||||
"A list of the ARM architectures to build (for a fat binary)."); | |||||
} | |||||
}; | |||||
BuildConfiguration::Ptr createBuildConfig (const ValueTree& v) const override | |||||
{ | |||||
return new AndroidBuildConfiguration (project, v, *this); | |||||
} | |||||
private: | |||||
//============================================================================== | |||||
String getToolchainVersion() const | |||||
{ | |||||
String v (getNDKToolchainVersionString()); | |||||
return v.isNotEmpty() ? v : "4.9"; | |||||
} | |||||
//============================================================================== | |||||
String getCppFlags() const | |||||
{ | |||||
String flags ("-fsigned-char -fexceptions -frtti"); | |||||
if (! getNDKToolchainVersionString().startsWithIgnoreCase ("clang")) | |||||
flags << " -Wno-psabi"; | |||||
return flags; | |||||
} | |||||
String getAppPlatform() const | |||||
{ | |||||
int ndkVersion = androidMinimumSDK.get().getIntValue(); | |||||
if (ndkVersion == 9) | |||||
ndkVersion = 10; // (doesn't seem to be a version '9') | |||||
return "android-" + String (ndkVersion); | |||||
} | |||||
void writeApplicationMk (const File& file) const | |||||
{ | |||||
MemoryOutputStream mo; | |||||
mo << "# Automatically generated makefile, created by the Projucer" << newLine | |||||
<< "# Don't edit this file! Your changes will be overwritten when you re-save the Projucer project!" << newLine | |||||
<< newLine | |||||
<< "APP_STL := gnustl_static" << newLine | |||||
<< "APP_CPPFLAGS += " << getCppFlags() << newLine | |||||
<< "APP_PLATFORM := " << getAppPlatform() << newLine | |||||
<< "NDK_TOOLCHAIN_VERSION := " << getToolchainVersion() << newLine | |||||
<< newLine | |||||
<< "ifeq ($(NDK_DEBUG),1)" << newLine | |||||
<< " APP_ABI := " << getABIs<AndroidBuildConfiguration> (true) << newLine | |||||
<< "else" << newLine | |||||
<< " APP_ABI := " << getABIs<AndroidBuildConfiguration> (false) << newLine | |||||
<< "endif" << newLine; | |||||
overwriteFileIfDifferentOrThrow (file, mo); | |||||
} | |||||
struct ShouldFileBeCompiledPredicate | |||||
{ | |||||
bool operator() (const Project::Item& projectItem) const { return projectItem.shouldBeCompiled(); } | |||||
}; | |||||
void writeAndroidMk (const File& file) const | |||||
{ | |||||
Array<RelativePath> files; | |||||
for (int i = 0; i < getAllGroups().size(); ++i) | |||||
findAllProjectItemsWithPredicate (getAllGroups().getReference(i), files, ShouldFileBeCompiledPredicate()); | |||||
MemoryOutputStream mo; | |||||
writeAndroidMk (mo, files); | |||||
overwriteFileIfDifferentOrThrow (file, mo); | |||||
} | |||||
void writeAndroidMkVariableList (OutputStream& out, const String& variableName, const String& settingsValue) const | |||||
{ | |||||
const StringArray separatedItems (getCommaOrWhitespaceSeparatedItems (settingsValue)); | |||||
if (separatedItems.size() > 0) | |||||
out << newLine << variableName << " := " << separatedItems.joinIntoString (" ") << newLine; | |||||
} | |||||
void writeAndroidMk (OutputStream& out, const Array<RelativePath>& files) const | |||||
{ | |||||
out << "# Automatically generated makefile, created by the Projucer" << newLine | |||||
<< "# Don't edit this file! Your changes will be overwritten when you re-save the Projucer project!" << newLine | |||||
<< newLine | |||||
<< "LOCAL_PATH := $(call my-dir)" << newLine | |||||
<< newLine | |||||
<< "include $(CLEAR_VARS)" << newLine | |||||
<< newLine | |||||
<< "ifeq ($(TARGET_ARCH_ABI), armeabi-v7a)" << newLine | |||||
<< " LOCAL_ARM_MODE := arm" << newLine | |||||
<< "endif" << newLine | |||||
<< newLine | |||||
<< "LOCAL_MODULE := juce_jni" << newLine | |||||
<< "LOCAL_SRC_FILES := \\" << newLine; | |||||
for (int i = 0; i < files.size(); ++i) | |||||
out << " " << (files.getReference(i).isAbsolute() ? "" : "../") | |||||
<< escapeSpaces (files.getReference(i).toUnixStyle()) << "\\" << newLine; | |||||
writeAndroidMkVariableList (out, "LOCAL_STATIC_LIBRARIES", getStaticLibrariesString()); | |||||
writeAndroidMkVariableList (out, "LOCAL_SHARED_LIBRARIES", getSharedLibrariesString()); | |||||
out << newLine | |||||
<< "ifeq ($(NDK_DEBUG),1)" << newLine; | |||||
writeConfigSettings (out, true); | |||||
out << "else" << newLine; | |||||
writeConfigSettings (out, false); | |||||
out << "endif" << newLine | |||||
<< newLine | |||||
<< "include $(BUILD_SHARED_LIBRARY)" << newLine; | |||||
StringArray importModules (getCommaOrWhitespaceSeparatedItems (getStaticLibrariesString())); | |||||
importModules.addArray (getCommaOrWhitespaceSeparatedItems (getSharedLibrariesString())); | |||||
for (int i = 0; i < importModules.size(); ++i) | |||||
out << "$(call import-module," << importModules[i] << ")" << newLine; | |||||
} | |||||
void writeConfigSettings (OutputStream& out, bool forDebug) const | |||||
{ | |||||
for (ConstConfigIterator config (*this); config.next();) | |||||
{ | |||||
if (config->isDebug() == forDebug) | |||||
{ | |||||
const AndroidBuildConfiguration& androidConfig = dynamic_cast<const AndroidBuildConfiguration&> (*config); | |||||
String cppFlags; | |||||
cppFlags << createCPPFlags (androidConfig) | |||||
<< (" " + replacePreprocessorTokens (androidConfig, getExtraCompilerFlagsString()).trim()).trimEnd() | |||||
<< newLine | |||||
<< getLDLIBS (androidConfig).trimEnd() | |||||
<< newLine; | |||||
out << " LOCAL_CPPFLAGS += " << cppFlags; | |||||
out << " LOCAL_CFLAGS += " << cppFlags; | |||||
break; | |||||
} | |||||
} | |||||
} | |||||
String getLDLIBS (const AndroidBuildConfiguration& config) const | |||||
{ | |||||
return " LOCAL_LDLIBS :=" + config.getGCCLibraryPathFlags() | |||||
+ " -llog -lGLESv2 -landroid -lEGL" + getExternalLibraryFlags (config) | |||||
+ " " + replacePreprocessorTokens (config, getExtraLinkerFlagsString()); | |||||
} | |||||
String createIncludePathFlags (const BuildConfiguration& config) const | |||||
{ | |||||
String flags; | |||||
StringArray searchPaths (extraSearchPaths); | |||||
searchPaths.addArray (config.getHeaderSearchPaths()); | |||||
searchPaths = getCleanedStringArray (searchPaths); | |||||
for (int i = 0; i < searchPaths.size(); ++i) | |||||
flags << " -I " << FileHelpers::unixStylePath (replacePreprocessorTokens (config, searchPaths[i])).quoted(); | |||||
return flags; | |||||
} | |||||
String createCPPFlags (const BuildConfiguration& config) const | |||||
{ | |||||
StringPairArray defines; | |||||
defines.set ("JUCE_ANDROID", "1"); | |||||
defines.set ("JUCE_ANDROID_API_VERSION", androidMinimumSDK.get()); | |||||
defines.set ("JUCE_ANDROID_ACTIVITY_CLASSNAME", getJNIActivityClassName().replaceCharacter ('/', '_')); | |||||
defines.set ("JUCE_ANDROID_ACTIVITY_CLASSPATH", "\\\"" + getJNIActivityClassName() + "\\\""); | |||||
String flags ("-fsigned-char -fexceptions -frtti"); | |||||
if (config.isDebug()) | |||||
{ | |||||
flags << " -g"; | |||||
defines.set ("DEBUG", "1"); | |||||
defines.set ("_DEBUG", "1"); | |||||
} | |||||
else | |||||
{ | |||||
defines.set ("NDEBUG", "1"); | |||||
} | |||||
flags << createIncludePathFlags (config) | |||||
<< " -O" << config.getGCCOptimisationFlag(); | |||||
flags << " -std=gnu++11"; | |||||
defines = mergePreprocessorDefs (defines, getAllPreprocessorDefs ()); | |||||
return flags + createGCCPreprocessorFlags (defines); | |||||
} | |||||
//============================================================================== | |||||
XmlElement* createAntBuildXML() const | |||||
{ | |||||
XmlElement* proj = new XmlElement ("project"); | |||||
proj->setAttribute ("name", projectName); | |||||
proj->setAttribute ("default", "debug"); | |||||
proj->createNewChildElement ("loadproperties")->setAttribute ("srcFile", "local.properties"); | |||||
proj->createNewChildElement ("loadproperties")->setAttribute ("srcFile", "project.properties"); | |||||
{ | |||||
XmlElement* target = proj->createNewChildElement ("target"); | |||||
target->setAttribute ("name", "clean"); | |||||
target->setAttribute ("depends", "android_rules.clean"); | |||||
target->createNewChildElement ("delete")->setAttribute ("dir", "libs"); | |||||
target->createNewChildElement ("delete")->setAttribute ("dir", "obj"); | |||||
XmlElement* executable = target->createNewChildElement ("exec"); | |||||
executable->setAttribute ("executable", "${ndk.dir}/ndk-build"); | |||||
executable->setAttribute ("dir", "${basedir}"); | |||||
executable->setAttribute ("failonerror", "true"); | |||||
executable->createNewChildElement ("arg")->setAttribute ("value", "clean"); | |||||
} | |||||
{ | |||||
XmlElement* target = proj->createNewChildElement ("target"); | |||||
target->setAttribute ("name", "-pre-build"); | |||||
addDebugConditionClause (target, "makefileConfig", "Debug", "Release"); | |||||
addDebugConditionClause (target, "ndkDebugValue", "NDK_DEBUG=1", "NDK_DEBUG=0"); | |||||
String debugABIs, releaseABIs; | |||||
for (ConstConfigIterator config (*this); config.next();) | |||||
{ | |||||
const AndroidBuildConfiguration& androidConfig = dynamic_cast<const AndroidBuildConfiguration&> (*config); | |||||
if (config->isDebug()) | |||||
debugABIs = androidConfig.getArchitectures(); | |||||
else | |||||
releaseABIs = androidConfig.getArchitectures(); | |||||
} | |||||
addDebugConditionClause (target, "app_abis", debugABIs, releaseABIs); | |||||
XmlElement* executable = target->createNewChildElement ("exec"); | |||||
executable->setAttribute ("executable", "${ndk.dir}/ndk-build"); | |||||
executable->setAttribute ("dir", "${basedir}"); | |||||
executable->setAttribute ("failonerror", "true"); | |||||
executable->createNewChildElement ("arg")->setAttribute ("value", "--jobs=4"); | |||||
executable->createNewChildElement ("arg")->setAttribute ("value", "CONFIG=${makefileConfig}"); | |||||
executable->createNewChildElement ("arg")->setAttribute ("value", "${ndkDebugValue}"); | |||||
executable->createNewChildElement ("arg")->setAttribute ("value", "APP_ABI=${app_abis}"); | |||||
target->createNewChildElement ("delete")->setAttribute ("file", "${out.final.file}"); | |||||
target->createNewChildElement ("delete")->setAttribute ("file", "${out.packaged.file}"); | |||||
} | |||||
proj->createNewChildElement ("import")->setAttribute ("file", "${sdk.dir}/tools/ant/build.xml"); | |||||
return proj; | |||||
} | |||||
void addDebugConditionClause (XmlElement* target, const String& property, | |||||
const String& debugValue, const String& releaseValue) const | |||||
{ | |||||
XmlElement* condition = target->createNewChildElement ("condition"); | |||||
condition->setAttribute ("property", property); | |||||
condition->setAttribute ("value", debugValue); | |||||
condition->setAttribute ("else", releaseValue); | |||||
XmlElement* equals = condition->createNewChildElement ("equals"); | |||||
equals->setAttribute ("arg1", "${ant.project.invoked-targets}"); | |||||
equals->setAttribute ("arg2", "debug"); | |||||
} | |||||
void writeProjectPropertiesFile (const File& file) const | |||||
{ | |||||
MemoryOutputStream mo; | |||||
mo << "# This file is used to override default values used by the Ant build system." << newLine | |||||
<< "# It is automatically generated - DO NOT EDIT IT or your changes will be lost!." << newLine | |||||
<< newLine | |||||
<< "target=" << getAppPlatform() << newLine | |||||
<< newLine; | |||||
overwriteFileIfDifferentOrThrow (file, mo); | |||||
} | |||||
void writeLocalPropertiesFile (const File& file) const | |||||
{ | |||||
MemoryOutputStream mo; | |||||
mo << "# This file is used to override default values used by the Ant build system." << newLine | |||||
<< "# It is automatically generated by the Projucer - DO NOT EDIT IT or your changes will be lost!." << newLine | |||||
<< newLine | |||||
<< "sdk.dir=" << escapeSpaces (replacePreprocessorDefs (getAllPreprocessorDefs(), sdkPath.toString())) << newLine | |||||
<< "ndk.dir=" << escapeSpaces (replacePreprocessorDefs (getAllPreprocessorDefs(), ndkPath.toString())) << newLine | |||||
<< "key.store=" << androidKeyStore.get() << newLine | |||||
<< "key.alias=" << androidKeyAlias.get() << newLine | |||||
<< "key.store.password=" << androidKeyStorePass.get() << newLine | |||||
<< "key.alias.password=" << androidKeyAliasPass.get() << newLine | |||||
<< newLine; | |||||
overwriteFileIfDifferentOrThrow (file, mo); | |||||
} | |||||
void writeStringsFile (const File& file) const | |||||
{ | |||||
XmlElement strings ("resources"); | |||||
XmlElement* resourceName = strings.createNewChildElement ("string"); | |||||
resourceName->setAttribute ("name", "app_name"); | |||||
resourceName->addTextElement (projectName); | |||||
writeXmlOrThrow (strings, file, "utf-8", 100); | |||||
} | |||||
//============================================================================== | |||||
JUCE_DECLARE_NON_COPYABLE (AndroidAntProjectExporter) | |||||
}; |
@@ -1,482 +0,0 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2015 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
class AndroidProjectExporterBase : public ProjectExporter | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
AndroidProjectExporterBase (Project& p, const ValueTree& t) | |||||
: ProjectExporter (p, t), | |||||
androidScreenOrientation (settings, Ids::androidScreenOrientation, nullptr, "unspecified"), | |||||
androidActivityClass (settings, Ids::androidActivityClass, nullptr, createDefaultClassName()), | |||||
androidActivitySubClassName (settings, Ids::androidActivitySubClassName, nullptr), | |||||
androidVersionCode (settings, Ids::androidVersionCode, nullptr, "1"), | |||||
androidMinimumSDK (settings, Ids::androidMinimumSDK, nullptr, "23"), | |||||
androidTheme (settings, Ids::androidTheme, nullptr), | |||||
androidInternetNeeded (settings, Ids::androidInternetNeeded, nullptr, true), | |||||
androidMicNeeded (settings, Ids::microphonePermissionNeeded, nullptr, false), | |||||
androidBluetoothNeeded (settings, Ids::androidBluetoothNeeded, nullptr, true), | |||||
androidOtherPermissions (settings, Ids::androidOtherPermissions, nullptr), | |||||
androidKeyStore (settings, Ids::androidKeyStore, nullptr, "${user.home}/.android/debug.keystore"), | |||||
androidKeyStorePass (settings, Ids::androidKeyStorePass, nullptr, "android"), | |||||
androidKeyAlias (settings, Ids::androidKeyAlias, nullptr, "androiddebugkey"), | |||||
androidKeyAliasPass (settings, Ids::androidKeyAliasPass, nullptr, "android") | |||||
{ | |||||
initialiseDependencyPathValues(); | |||||
} | |||||
//============================================================================== | |||||
bool isXcode() const override { return false; } | |||||
bool isVisualStudio() const override { return false; } | |||||
bool isCodeBlocks() const override { return false; } | |||||
bool isMakefile() const override { return false; } | |||||
bool isAndroid() const override { return true; } | |||||
bool isWindows() const override { return false; } | |||||
bool isLinux() const override { return false; } | |||||
bool isOSX() const override { return false; } | |||||
bool isiOS() const override { return false; } | |||||
bool supportsTargetType (ProjectType::Target::Type type) const override | |||||
{ | |||||
switch (type) | |||||
{ | |||||
case ProjectType::Target::GUIApp: | |||||
case ProjectType::Target::StaticLibrary: | |||||
return true; | |||||
default: | |||||
break; | |||||
} | |||||
return false; | |||||
} | |||||
//============================================================================== | |||||
void create (const OwnedArray<LibraryModule>& modules) const override | |||||
{ | |||||
const String package (getActivityClassPackage()); | |||||
const String path (package.replaceCharacter ('.', File::separator)); | |||||
const File target (getTargetFolder().getChildFile ("src").getChildFile (path)); | |||||
copyActivityJavaFiles (modules, target, package); | |||||
} | |||||
//============================================================================== | |||||
void addPlatformSpecificSettingsForProjectType (const ProjectType&) override | |||||
{ | |||||
// no-op. | |||||
} | |||||
//============================================================================== | |||||
void createExporterProperties (PropertyListBuilder& props) override | |||||
{ | |||||
createBaseExporterProperties (props); | |||||
createToolchainExporterProperties (props); | |||||
createManifestExporterProperties (props); | |||||
createLibraryModuleExporterProperties (props); | |||||
createCodeSigningExporterProperties (props); | |||||
createOtherExporterProperties (props); | |||||
} | |||||
//============================================================================== | |||||
enum ScreenOrientation | |||||
{ | |||||
unspecified = 1, | |||||
portrait = 2, | |||||
landscape = 3 | |||||
}; | |||||
//============================================================================== | |||||
CachedValue<String> androidScreenOrientation, androidActivityClass, androidActivitySubClassName, | |||||
androidVersionCode, androidMinimumSDK, androidTheme; | |||||
CachedValue<bool> androidInternetNeeded, androidMicNeeded, androidBluetoothNeeded; | |||||
CachedValue<String> androidOtherPermissions; | |||||
CachedValue<String> androidKeyStore, androidKeyStorePass, androidKeyAlias, androidKeyAliasPass; | |||||
//============================================================================== | |||||
void createBaseExporterProperties (PropertyListBuilder& props) | |||||
{ | |||||
static const char* orientations[] = { "Portrait and Landscape", "Portrait", "Landscape", nullptr }; | |||||
static const char* orientationValues[] = { "unspecified", "portrait", "landscape", nullptr }; | |||||
props.add (new ChoicePropertyComponent (androidScreenOrientation.getPropertyAsValue(), "Screen orientation", StringArray (orientations), Array<var> (orientationValues)), | |||||
"The screen orientations that this app should support"); | |||||
props.add (new TextWithDefaultPropertyComponent<String> (androidActivityClass, "Android Activity class name", 256), | |||||
"The full java class name to use for the app's Activity class."); | |||||
props.add (new TextPropertyComponent (androidActivitySubClassName.getPropertyAsValue(), "Android Activity sub-class name", 256, false), | |||||
"If not empty, specifies the Android Activity class name stored in the app's manifest. " | |||||
"Use this if you would like to use your own Android Activity sub-class."); | |||||
props.add (new TextWithDefaultPropertyComponent<String> (androidVersionCode, "Android Version Code", 32), | |||||
"An integer value that represents the version of the application code, relative to other versions."); | |||||
props.add (new DependencyPathPropertyComponent (project.getFile().getParentDirectory(), sdkPath, "Android SDK Path"), | |||||
"The path to the Android SDK folder on the target build machine"); | |||||
props.add (new DependencyPathPropertyComponent (project.getFile().getParentDirectory(), ndkPath, "Android NDK Path"), | |||||
"The path to the Android NDK folder on the target build machine"); | |||||
props.add (new TextWithDefaultPropertyComponent<String> (androidMinimumSDK, "Minimum SDK version", 32), | |||||
"The number of the minimum version of the Android SDK that the app requires"); | |||||
} | |||||
//============================================================================== | |||||
virtual void createToolchainExporterProperties (PropertyListBuilder& props) = 0; // different for ant and Android Studio | |||||
//============================================================================== | |||||
void createManifestExporterProperties (PropertyListBuilder& props) | |||||
{ | |||||
props.add (new BooleanPropertyComponent (androidInternetNeeded.getPropertyAsValue(), "Internet Access", "Specify internet access permission in the manifest"), | |||||
"If enabled, this will set the android.permission.INTERNET flag in the manifest."); | |||||
props.add (new BooleanPropertyComponent (androidMicNeeded.getPropertyAsValue(), "Audio Input Required", "Specify audio record permission in the manifest"), | |||||
"If enabled, this will set the android.permission.RECORD_AUDIO flag in the manifest."); | |||||
props.add (new BooleanPropertyComponent (androidBluetoothNeeded.getPropertyAsValue(), "Bluetooth permissions Required", "Specify bluetooth permission (required for Bluetooth MIDI)"), | |||||
"If enabled, this will set the android.permission.BLUETOOTH and android.permission.BLUETOOTH_ADMIN flag in the manifest. This is required for Bluetooth MIDI on Android."); | |||||
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."); | |||||
} | |||||
//============================================================================== | |||||
virtual void createLibraryModuleExporterProperties (PropertyListBuilder& props) = 0; // different for ant and Android Studio | |||||
//============================================================================== | |||||
void createCodeSigningExporterProperties (PropertyListBuilder& props) | |||||
{ | |||||
props.add (new TextWithDefaultPropertyComponent<String> (androidKeyStore, "Key Signing: key.store", 2048), | |||||
"The key.store value, used when signing the package."); | |||||
props.add (new TextWithDefaultPropertyComponent<String> (androidKeyStorePass, "Key Signing: key.store.password", 2048), | |||||
"The key.store password, used when signing the package."); | |||||
props.add (new TextWithDefaultPropertyComponent<String> (androidKeyAlias, "Key Signing: key.alias", 2048), | |||||
"The key.alias value, used when signing the package."); | |||||
props.add (new TextWithDefaultPropertyComponent<String> (androidKeyAliasPass, "Key Signing: key.alias.password", 2048), | |||||
"The key.alias password, used when signing the package."); | |||||
} | |||||
//============================================================================== | |||||
void createOtherExporterProperties (PropertyListBuilder& props) | |||||
{ | |||||
props.add (new TextPropertyComponent (androidTheme.getPropertyAsValue(), "Android Theme", 256, false), | |||||
"E.g. @android:style/Theme.NoTitleBar or leave blank for default"); | |||||
} | |||||
//============================================================================== | |||||
String createDefaultClassName() const | |||||
{ | |||||
String s (project.getBundleIdentifier().toString().toLowerCase()); | |||||
if (s.length() > 5 | |||||
&& s.containsChar ('.') | |||||
&& s.containsOnly ("abcdefghijklmnopqrstuvwxyz_.") | |||||
&& ! s.startsWithChar ('.')) | |||||
{ | |||||
if (! s.endsWithChar ('.')) | |||||
s << "."; | |||||
} | |||||
else | |||||
{ | |||||
s = "com.yourcompany."; | |||||
} | |||||
return s + CodeHelpers::makeValidIdentifier (project.getProjectFilenameRoot(), false, true, false); | |||||
} | |||||
void initialiseDependencyPathValues() | |||||
{ | |||||
sdkPath.referTo (Value (new DependencyPathValueSource (getSetting (Ids::androidSDKPath), | |||||
Ids::androidSDKPath, TargetOS::getThisOS()))); | |||||
ndkPath.referTo (Value (new DependencyPathValueSource (getSetting (Ids::androidNDKPath), | |||||
Ids::androidNDKPath, TargetOS::getThisOS()))); | |||||
} | |||||
void copyActivityJavaFiles (const OwnedArray<LibraryModule>& modules, const File& targetFolder, const String& package) const | |||||
{ | |||||
const String className (getActivityName()); | |||||
if (className.isEmpty()) | |||||
throw SaveError ("Invalid Android Activity class name: " + androidActivityClass.get()); | |||||
createDirectoryOrThrow (targetFolder); | |||||
LibraryModule* const coreModule = getCoreModule (modules); | |||||
if (coreModule != nullptr) | |||||
{ | |||||
File javaDestFile (targetFolder.getChildFile (className + ".java")); | |||||
File javaSourceFolder (coreModule->getFolder().getChildFile ("native") | |||||
.getChildFile ("java")); | |||||
String juceMidiCode, juceMidiImports, juceRuntimePermissionsCode; | |||||
juceMidiImports << newLine; | |||||
if (androidMinimumSDK.get().getIntValue() >= 23) | |||||
{ | |||||
File javaAndroidMidi (javaSourceFolder.getChildFile ("AndroidMidi.java")); | |||||
File javaRuntimePermissions (javaSourceFolder.getChildFile ("AndroidRuntimePermissions.java")); | |||||
juceMidiImports << "import android.media.midi.*;" << newLine | |||||
<< "import android.bluetooth.*;" << newLine | |||||
<< "import android.bluetooth.le.*;" << newLine; | |||||
juceMidiCode = javaAndroidMidi.loadFileAsString().replace ("JuceAppActivity", className); | |||||
juceRuntimePermissionsCode = javaRuntimePermissions.loadFileAsString().replace ("JuceAppActivity", className); | |||||
} | |||||
else | |||||
{ | |||||
juceMidiCode = javaSourceFolder.getChildFile ("AndroidMidiFallback.java") | |||||
.loadFileAsString() | |||||
.replace ("JuceAppActivity", className); | |||||
} | |||||
File javaSourceFile (javaSourceFolder.getChildFile ("JuceAppActivity.java")); | |||||
StringArray javaSourceLines (StringArray::fromLines (javaSourceFile.loadFileAsString())); | |||||
{ | |||||
MemoryOutputStream newFile; | |||||
for (int i = 0; i < javaSourceLines.size(); ++i) | |||||
{ | |||||
const String& line = javaSourceLines[i]; | |||||
if (line.contains ("$$JuceAndroidMidiImports$$")) | |||||
newFile << juceMidiImports; | |||||
else if (line.contains ("$$JuceAndroidMidiCode$$")) | |||||
newFile << juceMidiCode; | |||||
else if (line.contains ("$$JuceAndroidRuntimePermissionsCode$$")) | |||||
newFile << juceRuntimePermissionsCode; | |||||
else | |||||
newFile << line.replace ("JuceAppActivity", className) | |||||
.replace ("package com.juce;", "package " + package + ";") << newLine; | |||||
} | |||||
javaSourceLines = StringArray::fromLines (newFile.toString()); | |||||
} | |||||
while (javaSourceLines.size() > 2 | |||||
&& javaSourceLines[javaSourceLines.size() - 1].trim().isEmpty() | |||||
&& javaSourceLines[javaSourceLines.size() - 2].trim().isEmpty()) | |||||
javaSourceLines.remove (javaSourceLines.size() - 1); | |||||
overwriteFileIfDifferentOrThrow (javaDestFile, javaSourceLines.joinIntoString (newLine)); | |||||
} | |||||
} | |||||
String getActivityName() const | |||||
{ | |||||
return androidActivityClass.get().fromLastOccurrenceOf (".", false, false); | |||||
} | |||||
String getActivitySubClassName() const | |||||
{ | |||||
String activityPath = androidActivitySubClassName.get(); | |||||
return (activityPath.isEmpty()) ? getActivityName() : activityPath.fromLastOccurrenceOf (".", false, false); | |||||
} | |||||
String getActivityClassPackage() const | |||||
{ | |||||
return androidActivityClass.get().upToLastOccurrenceOf (".", false, false); | |||||
} | |||||
String getJNIActivityClassName() const | |||||
{ | |||||
return androidActivityClass.get().replaceCharacter ('.', '/'); | |||||
} | |||||
static LibraryModule* getCoreModule (const OwnedArray<LibraryModule>& modules) | |||||
{ | |||||
for (int i = modules.size(); --i >= 0;) | |||||
if (modules.getUnchecked(i)->getID() == "juce_core") | |||||
return modules.getUnchecked(i); | |||||
return nullptr; | |||||
} | |||||
StringArray getPermissionsRequired() const | |||||
{ | |||||
StringArray s; | |||||
s.addTokens (androidOtherPermissions.get(), ", ", ""); | |||||
if (androidInternetNeeded.get()) | |||||
s.add ("android.permission.INTERNET"); | |||||
if (androidMicNeeded.get()) | |||||
s.add ("android.permission.RECORD_AUDIO"); | |||||
if (androidBluetoothNeeded.get()) | |||||
{ | |||||
s.add ("android.permission.BLUETOOTH"); | |||||
s.add ("android.permission.BLUETOOTH_ADMIN"); | |||||
s.add ("android.permission.ACCESS_COARSE_LOCATION"); | |||||
} | |||||
return getCleanedStringArray (s); | |||||
} | |||||
template <typename PredicateT> | |||||
void findAllProjectItemsWithPredicate (const Project::Item& projectItem, Array<RelativePath>& results, const PredicateT& predicate) const | |||||
{ | |||||
if (projectItem.isGroup()) | |||||
{ | |||||
for (int i = 0; i < projectItem.getNumChildren(); ++i) | |||||
findAllProjectItemsWithPredicate (projectItem.getChild(i), results, predicate); | |||||
} | |||||
else | |||||
{ | |||||
if (predicate (projectItem)) | |||||
results.add (RelativePath (projectItem.getFile(), getTargetFolder(), RelativePath::buildTargetFolder)); | |||||
} | |||||
} | |||||
void writeIcon (const File& file, const Image& im) const | |||||
{ | |||||
if (im.isValid()) | |||||
{ | |||||
createDirectoryOrThrow (file.getParentDirectory()); | |||||
PNGImageFormat png; | |||||
MemoryOutputStream mo; | |||||
if (! png.writeImageToStream (im, mo)) | |||||
throw SaveError ("Can't generate Android icon file"); | |||||
overwriteFileIfDifferentOrThrow (file, mo); | |||||
} | |||||
} | |||||
void writeIcons (const File& folder) const | |||||
{ | |||||
ScopedPointer<Drawable> bigIcon (getBigIcon()); | |||||
ScopedPointer<Drawable> smallIcon (getSmallIcon()); | |||||
if (bigIcon != nullptr && smallIcon != nullptr) | |||||
{ | |||||
const int step = jmax (bigIcon->getWidth(), bigIcon->getHeight()) / 8; | |||||
writeIcon (folder.getChildFile ("drawable-xhdpi/icon.png"), getBestIconForSize (step * 8, false)); | |||||
writeIcon (folder.getChildFile ("drawable-hdpi/icon.png"), getBestIconForSize (step * 6, false)); | |||||
writeIcon (folder.getChildFile ("drawable-mdpi/icon.png"), getBestIconForSize (step * 4, false)); | |||||
writeIcon (folder.getChildFile ("drawable-ldpi/icon.png"), getBestIconForSize (step * 3, false)); | |||||
} | |||||
else if (Drawable* icon = bigIcon != nullptr ? bigIcon : smallIcon) | |||||
{ | |||||
writeIcon (folder.getChildFile ("drawable-mdpi/icon.png"), rescaleImageForIcon (*icon, icon->getWidth())); | |||||
} | |||||
} | |||||
template <typename BuildConfigType> | |||||
String getABIs (bool forDebug) const | |||||
{ | |||||
for (ConstConfigIterator config (*this); config.next();) | |||||
{ | |||||
const BuildConfigType& androidConfig = dynamic_cast<const BuildConfigType&> (*config); | |||||
if (config->isDebug() == forDebug) | |||||
return androidConfig.getArchitectures(); | |||||
} | |||||
return String(); | |||||
} | |||||
//============================================================================== | |||||
XmlElement* createManifestXML() const | |||||
{ | |||||
XmlElement* manifest = new XmlElement ("manifest"); | |||||
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()); | |||||
XmlElement* screens = manifest->createNewChildElement ("supports-screens"); | |||||
screens->setAttribute ("android:smallScreens", "true"); | |||||
screens->setAttribute ("android:normalScreens", "true"); | |||||
screens->setAttribute ("android:largeScreens", "true"); | |||||
//screens->setAttribute ("android:xlargeScreens", "true"); | |||||
screens->setAttribute ("android:anyDensity", "true"); | |||||
XmlElement* sdk = manifest->createNewChildElement ("uses-sdk"); | |||||
sdk->setAttribute ("android:minSdkVersion", androidMinimumSDK.get()); | |||||
sdk->setAttribute ("android:targetSdkVersion", androidMinimumSDK.get()); | |||||
{ | |||||
const StringArray permissions (getPermissionsRequired()); | |||||
for (int i = permissions.size(); --i >= 0;) | |||||
manifest->createNewChildElement ("uses-permission")->setAttribute ("android:name", permissions[i]); | |||||
} | |||||
if (project.getModules().isModuleEnabled ("juce_opengl")) | |||||
{ | |||||
XmlElement* feature = manifest->createNewChildElement ("uses-feature"); | |||||
feature->setAttribute ("android:glEsVersion", "0x00020000"); | |||||
feature->setAttribute ("android:required", "true"); | |||||
} | |||||
XmlElement* app = manifest->createNewChildElement ("application"); | |||||
app->setAttribute ("android:label", "@string/app_name"); | |||||
if (androidTheme.get().isNotEmpty()) | |||||
app->setAttribute ("android:theme", androidTheme.get()); | |||||
{ | |||||
ScopedPointer<Drawable> bigIcon (getBigIcon()), smallIcon (getSmallIcon()); | |||||
if (bigIcon != nullptr || smallIcon != nullptr) | |||||
app->setAttribute ("android:icon", "@drawable/icon"); | |||||
} | |||||
if (androidMinimumSDK.get().getIntValue() >= 11) | |||||
app->setAttribute ("android:hardwareAccelerated", "false"); // (using the 2D acceleration slows down openGL) | |||||
XmlElement* act = app->createNewChildElement ("activity"); | |||||
act->setAttribute ("android:name", getActivitySubClassName()); | |||||
act->setAttribute ("android:label", "@string/app_name"); | |||||
act->setAttribute ("android:configChanges", "keyboardHidden|orientation|screenSize"); | |||||
act->setAttribute ("android:screenOrientation", androidScreenOrientation.get()); | |||||
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"); | |||||
return manifest; | |||||
} | |||||
//============================================================================== | |||||
Value sdkPath, ndkPath; | |||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (AndroidProjectExporterBase) | |||||
}; |
@@ -1,842 +0,0 @@ | |||||
/* | |||||
============================================================================== | |||||
This file is part of the JUCE library. | |||||
Copyright (c) 2015 - ROLI Ltd. | |||||
Permission is granted to use this software under the terms of either: | |||||
a) the GPL v2 (or any later version) | |||||
b) the Affero GPL v3 | |||||
Details of these licenses can be found at: www.gnu.org/licenses | |||||
JUCE is distributed in the hope that it will be useful, but WITHOUT ANY | |||||
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR | |||||
A PARTICULAR PURPOSE. See the GNU General Public License for more details. | |||||
------------------------------------------------------------------------------ | |||||
To release a closed-source product which uses JUCE, commercial licenses are | |||||
available: visit www.juce.com for more information. | |||||
============================================================================== | |||||
*/ | |||||
class AndroidStudioProjectExporter : public AndroidProjectExporterBase | |||||
{ | |||||
public: | |||||
//============================================================================== | |||||
bool usesMMFiles() const override { return false; } | |||||
bool canCopeWithDuplicateFiles() override { return false; } | |||||
bool supportsUserDefinedConfigurations() const override { return false; } | |||||
bool isAndroidStudio() const override { return true; } | |||||
bool isAndroidAnt() const override { return false; } | |||||
static const char* getName() { return "Android Studio"; } | |||||
static const char* getValueTreeTypeName() { return "ANDROIDSTUDIO"; } | |||||
static AndroidStudioProjectExporter* createForSettings (Project& project, const ValueTree& settings) | |||||
{ | |||||
if (settings.hasType (getValueTreeTypeName())) | |||||
return new AndroidStudioProjectExporter (project, settings); | |||||
return nullptr; | |||||
} | |||||
//============================================================================== | |||||
CachedValue<String> gradleVersion, gradleWrapperVersion, gradleToolchain, buildToolsVersion; | |||||
//============================================================================== | |||||
AndroidStudioProjectExporter (Project& p, const ValueTree& t) | |||||
: AndroidProjectExporterBase (p, t), | |||||
gradleVersion (settings, Ids::gradleVersion, nullptr, "2.14.1"), | |||||
gradleWrapperVersion (settings, Ids::androidPluginVersion, nullptr, "0.8.1"), | |||||
gradleToolchain (settings, Ids::gradleToolchain, nullptr, "clang"), | |||||
buildToolsVersion (settings, Ids::buildToolsVersion, nullptr, "23.0.2"), | |||||
androidStudioExecutable (findAndroidStudioExecutable()) | |||||
{ | |||||
name = getName(); | |||||
if (getTargetLocationString().isEmpty()) | |||||
getTargetLocationValue() = getDefaultBuildsRootFolder() + "AndroidStudio"; | |||||
} | |||||
//============================================================================== | |||||
void createToolchainExporterProperties (PropertyListBuilder& props) override | |||||
{ | |||||
props.add (new TextWithDefaultPropertyComponent<String> (gradleVersion, "gradle version", 32), | |||||
"The version of gradle that Android Studio should use to build this app"); | |||||
props.add (new TextWithDefaultPropertyComponent<String> (gradleWrapperVersion, "gradle-experimental wrapper version", 32), | |||||
"The version of the gradle-experimental wrapper that Android Studio should use to build this app"); | |||||
static const char* toolchains[] = { "clang", "gcc", nullptr }; | |||||
props.add (new ChoicePropertyComponent (gradleToolchain.getPropertyAsValue(), "NDK Toolchain", StringArray (toolchains), Array<var> (toolchains)), | |||||
"The toolchain that gradle should invoke for NDK compilation (variable model.android.ndk.tooclhain in app/build.gradle)"); | |||||
props.add (new TextWithDefaultPropertyComponent<String> (buildToolsVersion, "Android build tools version", 32), | |||||
"The Android build tools version that Android Studio should use to build this app"); | |||||
} | |||||
void createLibraryModuleExporterProperties (PropertyListBuilder&) override | |||||
{ | |||||
// gradle cannot do native library modules as far as we know... | |||||
} | |||||
//============================================================================== | |||||
bool canLaunchProject() override | |||||
{ | |||||
return androidStudioExecutable.exists(); | |||||
} | |||||
bool launchProject() override | |||||
{ | |||||
if (! androidStudioExecutable.exists()) | |||||
{ | |||||
jassertfalse; | |||||
return false; | |||||
} | |||||
const File targetFolder (getTargetFolder()); | |||||
// we have to surround the path with extra quotes, otherwise Android Studio | |||||
// will choke if there are any space characters in the path. | |||||
return androidStudioExecutable.startAsProcess ("\"" + targetFolder.getFullPathName() + "\""); | |||||
} | |||||
//============================================================================== | |||||
void create (const OwnedArray<LibraryModule>& modules) const override | |||||
{ | |||||
const File targetFolder (getTargetFolder()); | |||||
removeOldFiles (targetFolder); | |||||
{ | |||||
const String package (getActivityClassPackage()); | |||||
const String path (package.replaceCharacter ('.', File::separator)); | |||||
const File javaTarget (targetFolder.getChildFile ("app/src/main/java").getChildFile (path)); | |||||
copyActivityJavaFiles (modules, javaTarget, package); | |||||
} | |||||
writeFile (targetFolder, "settings.gradle", getSettingsGradleFileContent()); | |||||
writeFile (targetFolder, "build.gradle", getProjectBuildGradleFileContent()); | |||||
writeFile (targetFolder, "app/build.gradle", getAppBuildGradleFileContent()); | |||||
writeFile (targetFolder, "local.properties", getLocalPropertiesFileContent()); | |||||
writeFile (targetFolder, "gradle/wrapper/gradle-wrapper.properties", getGradleWrapperPropertiesFileContent()); | |||||
writeBinaryFile (targetFolder, "gradle/wrapper/LICENSE-for-gradlewrapper.txt", BinaryData::LICENSE, BinaryData::LICENSESize); | |||||
writeBinaryFile (targetFolder, "gradle/wrapper/gradle-wrapper.jar", BinaryData::gradlewrapper_jar, BinaryData::gradlewrapper_jarSize); | |||||
writeBinaryFile (targetFolder, "gradlew", BinaryData::gradlew, BinaryData::gradlewSize); | |||||
writeBinaryFile (targetFolder, "gradlew.bat", BinaryData::gradlew_bat, BinaryData::gradlew_batSize); | |||||
targetFolder.getChildFile ("gradlew").setExecutePermission (true); | |||||
writeAndroidManifest (targetFolder); | |||||
writeStringsXML (targetFolder); | |||||
writeAppIcons (targetFolder); | |||||
createSourceSymlinks (targetFolder); | |||||
} | |||||
void removeOldFiles (const File& targetFolder) const | |||||
{ | |||||
targetFolder.getChildFile ("app/src").deleteRecursively(); | |||||
targetFolder.getChildFile ("app/build").deleteRecursively(); | |||||
targetFolder.getChildFile ("app/build.gradle").deleteFile(); | |||||
targetFolder.getChildFile ("gradle").deleteRecursively(); | |||||
targetFolder.getChildFile ("local.properties").deleteFile(); | |||||
targetFolder.getChildFile ("settings.gradle").deleteFile(); | |||||
} | |||||
void writeFile (const File& gradleProjectFolder, const String& filePath, const String& fileContent) const | |||||
{ | |||||
MemoryOutputStream outStream; | |||||
outStream << fileContent; | |||||
overwriteFileIfDifferentOrThrow (gradleProjectFolder.getChildFile (filePath), outStream); | |||||
} | |||||
void writeBinaryFile (const File& gradleProjectFolder, const String& filePath, const char* binaryData, const int binarySize) const | |||||
{ | |||||
MemoryOutputStream outStream; | |||||
outStream.write (binaryData, static_cast<size_t> (binarySize)); | |||||
overwriteFileIfDifferentOrThrow (gradleProjectFolder.getChildFile (filePath), outStream); | |||||
} | |||||
//============================================================================== | |||||
static File findAndroidStudioExecutable() | |||||
{ | |||||
#if JUCE_WINDOWS | |||||
const File defaultInstallation ("C:\\Program Files\\Android\\Android Studio\\bin"); | |||||
if (defaultInstallation.exists()) | |||||
{ | |||||
{ | |||||
const File studio64 = defaultInstallation.getChildFile ("studio64.exe"); | |||||
if (studio64.existsAsFile()) | |||||
return studio64; | |||||
} | |||||
{ | |||||
const File studio = defaultInstallation.getChildFile ("studio.exe"); | |||||
if (studio.existsAsFile()) | |||||
return studio; | |||||
} | |||||
} | |||||
#elif JUCE_MAC | |||||
const File defaultInstallation ("/Applications/Android Studio.app"); | |||||
if (defaultInstallation.exists()) | |||||
return defaultInstallation; | |||||
#endif | |||||
return File(); | |||||
} | |||||
protected: | |||||
//============================================================================== | |||||
class AndroidStudioBuildConfiguration : public BuildConfiguration | |||||
{ | |||||
public: | |||||
AndroidStudioBuildConfiguration (Project& p, const ValueTree& settings, const ProjectExporter& e) | |||||
: BuildConfiguration (p, settings, e) | |||||
{ | |||||
if (getArchitectures().isEmpty()) | |||||
{ | |||||
if (isDebug()) | |||||
getArchitecturesValue() = "armeabi x86"; | |||||
else | |||||
getArchitecturesValue() = "armeabi armeabi-v7a x86"; | |||||
} | |||||
} | |||||
Value getArchitecturesValue() { return getValue (Ids::androidArchitectures); } | |||||
String getArchitectures() const { return config [Ids::androidArchitectures]; } | |||||
var getDefaultOptimisationLevel() const override { return var ((int) (isDebug() ? gccO0 : gccO3)); } | |||||
void createConfigProperties (PropertyListBuilder& props) override | |||||
{ | |||||
addGCCOptimisationProperty (props); | |||||
props.add (new TextPropertyComponent (getArchitecturesValue(), "Architectures", 256, false), | |||||
"A list of the ARM architectures to build (for a fat binary)."); | |||||
} | |||||
}; | |||||
BuildConfiguration::Ptr createBuildConfig (const ValueTree& v) const override | |||||
{ | |||||
return new AndroidStudioBuildConfiguration (project, v, *this); | |||||
} | |||||
private: | |||||
static void createSymlinkAndParentFolders (const File& originalFile, const File& linkFile) | |||||
{ | |||||
{ | |||||
const File linkFileParentDirectory (linkFile.getParentDirectory()); | |||||
// this will recursively creative the parent directories for the file. | |||||
// without this, the symlink would fail because it doesn't automatically create | |||||
// the folders if they don't exist | |||||
if (! linkFileParentDirectory.createDirectory()) | |||||
throw SaveError (String ("Could not create directory ") + linkFileParentDirectory.getFullPathName()); | |||||
} | |||||
if (! originalFile.createSymbolicLink (linkFile, true)) | |||||
throw SaveError (String ("Failed to create symlink from ") | |||||
+ linkFile.getFullPathName() + " to " | |||||
+ originalFile.getFullPathName() + "!"); | |||||
} | |||||
void makeSymlinksForGroup (const Project::Item& group, const File& targetFolder) const | |||||
{ | |||||
if (! group.isGroup()) | |||||
{ | |||||
throw SaveError ("makeSymlinksForGroup was called with something other than a group!"); | |||||
} | |||||
for (int i = 0; i < group.getNumChildren(); ++i) | |||||
{ | |||||
const Project::Item& projectItem = group.getChild (i); | |||||
if (projectItem.isGroup()) | |||||
{ | |||||
makeSymlinksForGroup (projectItem, targetFolder.getChildFile (projectItem.getName())); | |||||
} | |||||
else if (projectItem.shouldBeAddedToTargetProject()) // must be a file then | |||||
{ | |||||
const File originalFile (projectItem.getFile()); | |||||
const File targetFile (targetFolder.getChildFile (originalFile.getFileName())); | |||||
createSymlinkAndParentFolders (originalFile, targetFile); | |||||
} | |||||
} | |||||
} | |||||
void createSourceSymlinks (const File& folder) const | |||||
{ | |||||
const File targetFolder (folder.getChildFile ("app/src/main/jni")); | |||||
// here we make symlinks to only to files included in the groups inside the project | |||||
// this is because Android Studio does not have a concept of groups and just uses | |||||
// the file system layout to determine what's to be compiled | |||||
{ | |||||
const Array<Project::Item>& groups = getAllGroups(); | |||||
for (int i = 0; i < groups.size(); ++i) | |||||
{ | |||||
const Project::Item projectItem (groups.getReference (i)); | |||||
const String projectItemName (projectItem.getName()); | |||||
if (projectItem.isGroup()) | |||||
makeSymlinksForGroup (projectItem, projectItemName == "Juce Modules" ? targetFolder.getChildFile ("JuceModules") : targetFolder); | |||||
} | |||||
} | |||||
} | |||||
void writeAppIcons (const File& folder) const | |||||
{ | |||||
writeIcons (folder.getChildFile ("app/src/main/res/")); | |||||
} | |||||
static String sanitisePath (String path) | |||||
{ | |||||
return expandHomeFolderToken (path).replace ("\\", "\\\\"); | |||||
} | |||||
static String expandHomeFolderToken (const String& path) | |||||
{ | |||||
String homeFolder = File::getSpecialLocation (File::userHomeDirectory).getFullPathName(); | |||||
return path.replace ("${user.home}", homeFolder) | |||||
.replace ("~", homeFolder); | |||||
} | |||||
//============================================================================== | |||||
struct GradleElement | |||||
{ | |||||
virtual ~GradleElement() {} | |||||
String toString() const { return toStringIndented (0); } | |||||
virtual String toStringIndented (int indentLevel) const = 0; | |||||
static String indent (int indentLevel) { return String::repeatedString (" ", indentLevel); } | |||||
}; | |||||
//============================================================================== | |||||
struct GradleStatement : public GradleElement | |||||
{ | |||||
GradleStatement (const String& s) : statement (s) {} | |||||
String toStringIndented (int indentLevel) const override { return indent (indentLevel) + statement; } | |||||
String statement; | |||||
}; | |||||
//============================================================================== | |||||
struct GradleCppFlag : public GradleStatement | |||||
{ | |||||
GradleCppFlag (const String& flag) | |||||
: GradleStatement ("cppFlags.add(" + flag.quoted() + ")") {} | |||||
}; | |||||
struct GradlePreprocessorDefine : public GradleStatement | |||||
{ | |||||
GradlePreprocessorDefine (const String& define, const String& value) | |||||
: GradleStatement ("cppFlags.add(\"-D" + define + "=" + value + "\")") {} | |||||
}; | |||||
struct GradleHeaderIncludePath : public GradleStatement | |||||
{ | |||||
GradleHeaderIncludePath (const String& path) | |||||
: GradleStatement ("cppFlags.add(\"-I${project.rootDir}/" + sanitisePath (path) + "\".toString())") {} | |||||
}; | |||||
struct GradleLibrarySearchPath : public GradleStatement | |||||
{ | |||||
GradleLibrarySearchPath (const String& path) | |||||
: GradleStatement ("cppFlags.add(\"-L" + sanitisePath (path) + "\".toString())") {} | |||||
}; | |||||
struct GradleLinkerFlag : public GradleStatement | |||||
{ | |||||
GradleLinkerFlag (const String& flag) | |||||
: GradleStatement ("ldFlags.add(" + flag.quoted() + "\")") {} | |||||
}; | |||||
struct GradleLinkLibrary : public GradleStatement | |||||
{ | |||||
GradleLinkLibrary (const String& lib) | |||||
: GradleStatement ("ldLibs.add(" + lib.quoted() + ")") {} | |||||
}; | |||||
//============================================================================== | |||||
struct GradleValue : public GradleElement | |||||
{ | |||||
template <typename ValueType> | |||||
GradleValue (const String& k, const ValueType& v) | |||||
: key (k), value (v) {} | |||||
GradleValue (const String& k, bool boolValue) | |||||
: key (k), value (boolValue ? "true" : "false") {} | |||||
String toStringIndented (int indentLevel) const override | |||||
{ | |||||
return indent (indentLevel) + key + " = " + value; | |||||
} | |||||
protected: | |||||
String key, value; | |||||
}; | |||||
struct GradleString : public GradleValue | |||||
{ | |||||
GradleString (const String& k, const String& str) | |||||
: GradleValue (k, str.quoted()) | |||||
{ | |||||
if (str.containsAnyOf ("${\"\'")) | |||||
value += ".toString()"; | |||||
} | |||||
}; | |||||
struct GradleFilePath : public GradleValue | |||||
{ | |||||
GradleFilePath (const String& k, const String& path) | |||||
: GradleValue (k, "new File(\"" + sanitisePath (path) + "\")") {} | |||||
}; | |||||
//============================================================================== | |||||
struct GradleObject : public GradleElement | |||||
{ | |||||
GradleObject (const String& nm) : name (nm) {} | |||||
#if JUCE_COMPILER_SUPPORTS_VARIADIC_TEMPLATES | |||||
template <typename GradleType, typename... Args> | |||||
void add (Args... args) | |||||
{ | |||||
children.add (new GradleType (args...)); | |||||
// Note: can't use std::forward because it doesn't compile for OS X 10.8 | |||||
} | |||||
#else // Remove this workaround once we drop VS2012 support! | |||||
template <typename GradleType, typename Arg1> | |||||
void add (Arg1 arg1) | |||||
{ | |||||
children.add (new GradleType (arg1)); | |||||
} | |||||
template <typename GradleType, typename Arg1, typename Arg2> | |||||
void add (Arg1 arg1, Arg2 arg2) | |||||
{ | |||||
children.add (new GradleType (arg1, arg2)); | |||||
} | |||||
#endif | |||||
void addChildObject (GradleObject* objectToAdd) noexcept | |||||
{ | |||||
children.add (objectToAdd); | |||||
} | |||||
String toStringIndented (int indentLevel) const override | |||||
{ | |||||
String result; | |||||
result << indent (indentLevel) << name << " {" << newLine; | |||||
for (const auto& child : children) | |||||
result << child->toStringIndented (indentLevel + 1) << newLine; | |||||
result << indent (indentLevel) << "}"; | |||||
if (indentLevel == 0) | |||||
result << newLine; | |||||
return result; | |||||
} | |||||
private: | |||||
String name; | |||||
OwnedArray<GradleElement> children; | |||||
}; | |||||
//============================================================================== | |||||
String getSettingsGradleFileContent() const | |||||
{ | |||||
return "include ':app'"; | |||||
} | |||||
String getProjectBuildGradleFileContent() const | |||||
{ | |||||
String projectBuildGradle; | |||||
projectBuildGradle << getGradleBuildScript(); | |||||
projectBuildGradle << getGradleAllProjects(); | |||||
return projectBuildGradle; | |||||
} | |||||
//============================================================================== | |||||
String getGradleBuildScript() const | |||||
{ | |||||
GradleObject buildScript ("buildscript"); | |||||
buildScript.addChildObject (getGradleRepositories()); | |||||
buildScript.addChildObject (getGradleDependencies()); | |||||
return buildScript.toString(); | |||||
} | |||||
GradleObject* getGradleRepositories() const | |||||
{ | |||||
auto repositories = new GradleObject ("repositories"); | |||||
repositories->add<GradleStatement> ("jcenter()"); | |||||
return repositories; | |||||
} | |||||
GradleObject* getGradleDependencies() const | |||||
{ | |||||
auto dependencies = new GradleObject ("dependencies"); | |||||
dependencies->add<GradleStatement> ("classpath 'com.android.tools.build:gradle-experimental:" | |||||
+ gradleWrapperVersion.get() + "'"); | |||||
return dependencies; | |||||
} | |||||
String getGradleAllProjects() const | |||||
{ | |||||
GradleObject allProjects ("allprojects"); | |||||
allProjects.addChildObject (getGradleRepositories()); | |||||
return allProjects.toString(); | |||||
} | |||||
//============================================================================== | |||||
String getAppBuildGradleFileContent() const | |||||
{ | |||||
String appBuildGradle; | |||||
appBuildGradle << "apply plugin: 'com.android.model.application'" << newLine; | |||||
appBuildGradle << getAndroidModel(); | |||||
appBuildGradle << getAppDependencies(); | |||||
return appBuildGradle; | |||||
} | |||||
String getAndroidModel() const | |||||
{ | |||||
GradleObject model ("model"); | |||||
model.addChildObject (getAndroidObject()); | |||||
model.addChildObject (getAndroidNdkSettings()); | |||||
model.addChildObject (getAndroidSources()); | |||||
model.addChildObject (getAndroidBuildConfigs()); | |||||
model.addChildObject (getAndroidSigningConfigs()); | |||||
model.addChildObject (getAndroidProductFlavours()); | |||||
return model.toString(); | |||||
} | |||||
String getAppDependencies() const | |||||
{ | |||||
GradleObject dependencies ("dependencies"); | |||||
dependencies.add<GradleStatement> ("compile \"com.android.support:support-v4:+\""); | |||||
return dependencies.toString(); | |||||
} | |||||
//============================================================================== | |||||
GradleObject* getAndroidObject() const | |||||
{ | |||||
auto android = new GradleObject ("android"); | |||||
android->add<GradleValue> ("compileSdkVersion", androidMinimumSDK.get().getIntValue()); | |||||
android->add<GradleString> ("buildToolsVersion", buildToolsVersion.get()); | |||||
android->addChildObject (getAndroidDefaultConfig()); | |||||
return android; | |||||
} | |||||
GradleObject* getAndroidDefaultConfig() const | |||||
{ | |||||
const String bundleIdentifier = project.getBundleIdentifier().toString().toLowerCase(); | |||||
const int minSdkVersion = androidMinimumSDK.get().getIntValue(); | |||||
auto defaultConfig = new GradleObject ("defaultConfig.with"); | |||||
defaultConfig->add<GradleString> ("applicationId", bundleIdentifier); | |||||
defaultConfig->add<GradleValue> ("minSdkVersion.apiLevel", minSdkVersion); | |||||
defaultConfig->add<GradleValue> ("targetSdkVersion.apiLevel", minSdkVersion); | |||||
return defaultConfig; | |||||
} | |||||
GradleObject* getAndroidNdkSettings() const | |||||
{ | |||||
const String toolchain = gradleToolchain.get(); | |||||
const bool isClang = (toolchain == "clang"); | |||||
auto ndkSettings = new GradleObject ("android.ndk"); | |||||
ndkSettings->add<GradleString> ("moduleName", "juce_jni"); | |||||
ndkSettings->add<GradleString> ("toolchain", toolchain); | |||||
ndkSettings->add<GradleString> ("stl", isClang ? "c++_static" : "gnustl_static"); | |||||
addAllNdkCompilerSettings (ndkSettings); | |||||
return ndkSettings; | |||||
} | |||||
void addAllNdkCompilerSettings (GradleObject* ndk) const | |||||
{ | |||||
addNdkCppFlags (ndk); | |||||
addNdkPreprocessorDefines (ndk); | |||||
addNdkHeaderIncludePaths (ndk); | |||||
addNdkLinkerFlags (ndk); | |||||
addNdkLibraries (ndk); | |||||
} | |||||
void addNdkCppFlags (GradleObject* ndk) const | |||||
{ | |||||
const char* alwaysUsedFlags[] = { "-fsigned-char", "-fexceptions", "-frtti", "-std=c++11", nullptr }; | |||||
StringArray cppFlags (alwaysUsedFlags); | |||||
cppFlags.mergeArray (StringArray::fromTokens (getExtraCompilerFlagsString(), " ", "")); | |||||
for (int i = 0; i < cppFlags.size(); ++i) | |||||
ndk->add<GradleCppFlag> (cppFlags[i]); | |||||
} | |||||
void addNdkPreprocessorDefines (GradleObject* ndk) const | |||||
{ | |||||
const auto& defines = getAllPreprocessorDefs(); | |||||
for (int i = 0; i < defines.size(); ++i) | |||||
ndk->add<GradlePreprocessorDefine> ( defines.getAllKeys()[i], defines.getAllValues()[i]); | |||||
} | |||||
void addNdkHeaderIncludePaths (GradleObject* ndk) const | |||||
{ | |||||
StringArray includePaths; | |||||
for (const auto& cppFile : getAllCppFilesToBeIncludedWithPath()) | |||||
includePaths.addIfNotAlreadyThere (cppFile.getParentDirectory().toUnixStyle()); | |||||
for (const auto& path : includePaths) | |||||
ndk->add<GradleHeaderIncludePath> (path); | |||||
} | |||||
Array<RelativePath> getAllCppFilesToBeIncludedWithPath() const | |||||
{ | |||||
Array<RelativePath> cppFiles; | |||||
struct NeedsToBeIncludedWithPathPredicate | |||||
{ | |||||
bool operator() (const Project::Item& projectItem) const | |||||
{ | |||||
return projectItem.shouldBeAddedToTargetProject() && ! projectItem.isModuleCode(); | |||||
} | |||||
}; | |||||
for (const auto& group : getAllGroups()) | |||||
findAllProjectItemsWithPredicate (group, cppFiles, NeedsToBeIncludedWithPathPredicate()); | |||||
return cppFiles; | |||||
} | |||||
void addNdkLinkerFlags (GradleObject* ndk) const | |||||
{ | |||||
const auto linkerFlags = StringArray::fromTokens (getExtraLinkerFlagsString(), " ", ""); | |||||
for (const auto& flag : linkerFlags) | |||||
ndk->add<GradleLinkerFlag> (flag); | |||||
} | |||||
void addNdkLibraries (GradleObject* ndk) const | |||||
{ | |||||
const char* requiredAndroidLibs[] = { "android", "EGL", "GLESv2", "log", nullptr }; | |||||
StringArray libs (requiredAndroidLibs); | |||||
libs.addArray (StringArray::fromTokens(getExternalLibrariesString(), ";", "")); | |||||
for (const auto& lib : libs) | |||||
ndk->add<GradleLinkLibrary> (lib); | |||||
} | |||||
GradleObject* getAndroidSources() const | |||||
{ | |||||
auto source = new GradleObject ("source"); // app source folder | |||||
source->add<GradleStatement> ("exclude \"**/JuceModules/\""); | |||||
auto jni = new GradleObject ("jni"); // all C++ sources for app | |||||
jni->addChildObject (source); | |||||
auto main = new GradleObject ("main"); // all sources for app | |||||
main->addChildObject (jni); | |||||
auto sources = new GradleObject ("android.sources"); // all sources | |||||
sources->addChildObject (main); | |||||
return sources; | |||||
} | |||||
GradleObject* getAndroidBuildConfigs() const | |||||
{ | |||||
auto buildConfigs = new GradleObject ("android.buildTypes"); | |||||
for (ConstConfigIterator config (*this); config.next();) | |||||
buildConfigs->addChildObject (getBuildConfig (*config)); | |||||
return buildConfigs; | |||||
} | |||||
GradleObject* getBuildConfig (const BuildConfiguration& config) const | |||||
{ | |||||
const String configName (config.getName()); | |||||
// Note: at the moment, Android Studio only supports a "debug" and a "release" | |||||
// build config, but no custom build configs like Projucer's other exporters do. | |||||
if (configName != "Debug" && configName != "Release") | |||||
throw SaveError ("Build configurations other than Debug and Release are not yet support for Android Studio"); | |||||
auto gradleConfig = new GradleObject (configName.toLowerCase()); | |||||
if (! config.isDebug()) | |||||
gradleConfig->add<GradleValue> ("signingConfig", "$(\"android.signingConfigs.releaseConfig\")"); | |||||
addConfigNdkSettings (gradleConfig, config); | |||||
return gradleConfig; | |||||
} | |||||
void addConfigNdkSettings (GradleObject* buildConfig, const BuildConfiguration& config) const | |||||
{ | |||||
auto ndkSettings = new GradleObject ("ndk.with"); | |||||
if (config.isDebug()) | |||||
{ | |||||
ndkSettings->add<GradleValue> ("debuggable", true); | |||||
ndkSettings->add<GradleCppFlag> ("-g"); | |||||
ndkSettings->add<GradlePreprocessorDefine> ("DEBUG", "1"); | |||||
ndkSettings->add<GradlePreprocessorDefine> ("_DEBUG", "1"); | |||||
} | |||||
else | |||||
{ | |||||
ndkSettings->add<GradlePreprocessorDefine> ("NDEBUG", "1"); | |||||
} | |||||
ndkSettings->add<GradleCppFlag> ("-O" + config.getGCCOptimisationFlag()); | |||||
for (const auto& path : getHeaderSearchPaths (config)) | |||||
ndkSettings->add<GradleHeaderIncludePath> (path); | |||||
for (const auto& path : config.getLibrarySearchPaths()) | |||||
ndkSettings->add<GradleLibrarySearchPath> (path); | |||||
ndkSettings->add<GradlePreprocessorDefine> ("JUCE_ANDROID", "1"); | |||||
ndkSettings->add<GradlePreprocessorDefine> ("JUCE_ANDROID_API_VERSION", androidMinimumSDK.get()); | |||||
ndkSettings->add<GradlePreprocessorDefine> ("JUCE_ANDROID_ACTIVITY_CLASSNAME", getJNIActivityClassName().replaceCharacter ('/', '_')); | |||||
ndkSettings->add<GradlePreprocessorDefine> ("JUCE_ANDROID_ACTIVITY_CLASSPATH","\\\"" + androidActivityClass.get().replaceCharacter('.', '/') + "\\\""); | |||||
const auto defines = config.getAllPreprocessorDefs(); | |||||
for (int i = 0; i < defines.size(); ++i) | |||||
ndkSettings->add<GradlePreprocessorDefine> (defines.getAllKeys()[i], defines.getAllValues()[i]); | |||||
buildConfig->addChildObject (ndkSettings); | |||||
} | |||||
StringArray getHeaderSearchPaths (const BuildConfiguration& config) const | |||||
{ | |||||
StringArray paths (extraSearchPaths); | |||||
paths.addArray (config.getHeaderSearchPaths()); | |||||
paths = getCleanedStringArray (paths); | |||||
return paths; | |||||
} | |||||
GradleObject* getAndroidSigningConfigs() const | |||||
{ | |||||
auto releaseConfig = new GradleObject ("create(\"releaseConfig\")"); | |||||
releaseConfig->add<GradleFilePath> ("storeFile", androidKeyStore.get()); | |||||
releaseConfig->add<GradleString> ("storePassword", androidKeyStorePass.get()); | |||||
releaseConfig->add<GradleString> ("keyAlias", androidKeyAlias.get()); | |||||
releaseConfig->add<GradleString> ("keyPassword", androidKeyAliasPass.get()); | |||||
releaseConfig->add<GradleString> ("storeType", "jks"); | |||||
auto signingConfigs = new GradleObject ("android.signingConfigs"); | |||||
signingConfigs->addChildObject (releaseConfig); | |||||
// Note: no need to add a debugConfig, Android Studio will use debug.keystore by default | |||||
return signingConfigs; | |||||
} | |||||
GradleObject* getAndroidProductFlavours() const | |||||
{ | |||||
auto flavours = new GradleObject ("android.productFlavors"); | |||||
StringArray architectures (StringArray::fromTokens (getABIs<AndroidStudioBuildConfiguration> (true), " ", "")); | |||||
architectures.mergeArray (StringArray::fromTokens (getABIs<AndroidStudioBuildConfiguration> (false), " ", "")); | |||||
if (architectures.size() == 0) | |||||
throw SaveError ("Can't build for no architectures!"); | |||||
for (int i = 0; i < architectures.size(); ++i) | |||||
{ | |||||
String arch (architectures[i].trim()); | |||||
if ((arch).isEmpty()) | |||||
continue; | |||||
flavours->addChildObject (getGradleProductFlavourForArch (arch)); | |||||
} | |||||
return flavours; | |||||
} | |||||
GradleObject* getGradleProductFlavourForArch (const String& arch) const | |||||
{ | |||||
auto flavour = new GradleObject ("create(\"" + arch + "\")"); | |||||
flavour->add<GradleStatement> ("ndk.abiFilters.add(\"" + arch + "\")"); | |||||
return flavour; | |||||
} | |||||
//============================================================================== | |||||
String getLocalPropertiesFileContent() const | |||||
{ | |||||
String props; | |||||
props << "ndk.dir=" << sanitisePath (ndkPath.toString()) << newLine | |||||
<< "sdk.dir=" << sanitisePath (sdkPath.toString()) << newLine; | |||||
return props; | |||||
} | |||||
String getGradleWrapperPropertiesFileContent() const | |||||
{ | |||||
String props; | |||||
props << "distributionUrl=https\\://services.gradle.org/distributions/gradle-" | |||||
<< gradleVersion.get() << "-all.zip"; | |||||
return props; | |||||
} | |||||
//============================================================================== | |||||
void writeStringsXML (const File& folder) const | |||||
{ | |||||
XmlElement strings ("resources"); | |||||
XmlElement* resourceName = strings.createNewChildElement ("string"); | |||||
resourceName->setAttribute ("name", "app_name"); | |||||
resourceName->addTextElement (projectName); | |||||
writeXmlOrThrow (strings, folder.getChildFile ("app/src/main/res/values/string.xml"), "utf-8", 100, true); | |||||
} | |||||
//============================================================================== | |||||
void writeAndroidManifest (const File& folder) const | |||||
{ | |||||
ScopedPointer<XmlElement> manifest (createManifestXML()); | |||||
writeXmlOrThrow (*manifest, folder.getChildFile ("app/src/main/AndroidManifest.xml"), "utf-8", 100, true); | |||||
} | |||||
//============================================================================== | |||||
const File androidStudioExecutable; | |||||
JUCE_DECLARE_NON_COPYABLE (AndroidStudioProjectExporter) | |||||
}; |
@@ -105,7 +105,6 @@ public: | |||||
bool isCodeBlocks() const override { return true; } | bool isCodeBlocks() const override { return true; } | ||||
bool isMakefile() const override { return false; } | bool isMakefile() const override { return false; } | ||||
bool isAndroidStudio() const override { return false; } | bool isAndroidStudio() const override { return false; } | ||||
bool isAndroidAnt() const override { return false; } | |||||
bool isAndroid() const override { return false; } | bool isAndroid() const override { return false; } | ||||
bool isWindows() const override { return os == windowsTarget; } | bool isWindows() const override { return os == windowsTarget; } | ||||
@@ -536,7 +536,6 @@ public: | |||||
bool isCodeBlocks() const override { return false; } | bool isCodeBlocks() const override { return false; } | ||||
bool isMakefile() const override { return false; } | bool isMakefile() const override { return false; } | ||||
bool isAndroidStudio() const override { return false; } | bool isAndroidStudio() const override { return false; } | ||||
bool isAndroidAnt() const override { return false; } | |||||
bool isAndroid() const override { return false; } | bool isAndroid() const override { return false; } | ||||
bool isWindows() const override { return true; } | bool isWindows() const override { return true; } | ||||
@@ -300,7 +300,6 @@ public: | |||||
bool isCodeBlocks() const override { return false; } | bool isCodeBlocks() const override { return false; } | ||||
bool isMakefile() const override { return true; } | bool isMakefile() const override { return true; } | ||||
bool isAndroidStudio() const override { return false; } | bool isAndroidStudio() const override { return false; } | ||||
bool isAndroidAnt() const override { return false; } | |||||
bool isAndroid() const override { return false; } | bool isAndroid() const override { return false; } | ||||
bool isWindows() const override { return false; } | bool isWindows() const override { return false; } | ||||
@@ -119,7 +119,6 @@ public: | |||||
bool isCodeBlocks() const override { return false; } | bool isCodeBlocks() const override { return false; } | ||||
bool isMakefile() const override { return false; } | bool isMakefile() const override { return false; } | ||||
bool isAndroidStudio() const override { return false; } | bool isAndroidStudio() const override { return false; } | ||||
bool isAndroidAnt() const override { return false; } | |||||
bool isAndroid() const override { return false; } | bool isAndroid() const override { return false; } | ||||
bool isWindows() const override { return false; } | bool isWindows() const override { return false; } | ||||
@@ -29,9 +29,7 @@ | |||||
#include "jucer_ProjectExport_Make.h" | #include "jucer_ProjectExport_Make.h" | ||||
#include "jucer_ProjectExport_MSVC.h" | #include "jucer_ProjectExport_MSVC.h" | ||||
#include "jucer_ProjectExport_XCode.h" | #include "jucer_ProjectExport_XCode.h" | ||||
#include "jucer_ProjectExport_AndroidBase.h" | |||||
#include "jucer_ProjectExport_AndroidStudio.h" | |||||
#include "jucer_ProjectExport_AndroidAnt.h" | |||||
#include "jucer_ProjectExport_Android.h" | |||||
#include "jucer_ProjectExport_CodeBlocks.h" | #include "jucer_ProjectExport_CodeBlocks.h" | ||||
//============================================================================== | //============================================================================== | ||||
@@ -55,8 +53,7 @@ Array<ProjectExporter::ExporterTypeInfo> ProjectExporter::getExporterTypes() | |||||
addType (types, MSVCProjectExporterVC2008::getName(), BinaryData::projectIconVisualStudio_png, BinaryData::projectIconVisualStudio_pngSize); | addType (types, MSVCProjectExporterVC2008::getName(), BinaryData::projectIconVisualStudio_png, BinaryData::projectIconVisualStudio_pngSize); | ||||
addType (types, MSVCProjectExporterVC2005::getName(), BinaryData::projectIconVisualStudio_png, BinaryData::projectIconVisualStudio_pngSize); | addType (types, MSVCProjectExporterVC2005::getName(), BinaryData::projectIconVisualStudio_png, BinaryData::projectIconVisualStudio_pngSize); | ||||
addType (types, MakefileProjectExporter::getNameLinux(), BinaryData::projectIconLinuxMakefile_png, BinaryData::projectIconLinuxMakefile_pngSize); | addType (types, MakefileProjectExporter::getNameLinux(), BinaryData::projectIconLinuxMakefile_png, BinaryData::projectIconLinuxMakefile_pngSize); | ||||
addType (types, AndroidStudioProjectExporter::getName(), BinaryData::projectIconAndroid_png, BinaryData::projectIconAndroid_pngSize); | |||||
addType (types, AndroidAntProjectExporter::getName(), BinaryData::projectIconAndroid_png, BinaryData::projectIconAndroid_pngSize); | |||||
addType (types, AndroidProjectExporter::getName(), BinaryData::projectIconAndroid_png, BinaryData::projectIconAndroid_pngSize); | |||||
addType (types, CodeBlocksProjectExporter::getNameWindows(), BinaryData::projectIconCodeblocks_png, BinaryData::projectIconCodeblocks_pngSize); | addType (types, CodeBlocksProjectExporter::getNameWindows(), BinaryData::projectIconCodeblocks_png, BinaryData::projectIconCodeblocks_pngSize); | ||||
addType (types, CodeBlocksProjectExporter::getNameLinux(), BinaryData::projectIconCodeblocks_png, BinaryData::projectIconCodeblocks_pngSize); | addType (types, CodeBlocksProjectExporter::getNameLinux(), BinaryData::projectIconCodeblocks_png, BinaryData::projectIconCodeblocks_pngSize); | ||||
@@ -78,10 +75,9 @@ ProjectExporter* ProjectExporter::createNewExporter (Project& project, const int | |||||
case 6: exp = new MSVCProjectExporterVC2008 (project, ValueTree (MSVCProjectExporterVC2008 ::getValueTreeTypeName())); break; | case 6: exp = new MSVCProjectExporterVC2008 (project, ValueTree (MSVCProjectExporterVC2008 ::getValueTreeTypeName())); break; | ||||
case 7: exp = new MSVCProjectExporterVC2005 (project, ValueTree (MSVCProjectExporterVC2005 ::getValueTreeTypeName())); break; | case 7: exp = new MSVCProjectExporterVC2005 (project, ValueTree (MSVCProjectExporterVC2005 ::getValueTreeTypeName())); break; | ||||
case 8: exp = new MakefileProjectExporter (project, ValueTree (MakefileProjectExporter ::getValueTreeTypeName())); break; | case 8: exp = new MakefileProjectExporter (project, ValueTree (MakefileProjectExporter ::getValueTreeTypeName())); break; | ||||
case 9: exp = new AndroidStudioProjectExporter (project, ValueTree (AndroidStudioProjectExporter ::getValueTreeTypeName())); break; | |||||
case 10: exp = new AndroidAntProjectExporter (project, ValueTree (AndroidAntProjectExporter ::getValueTreeTypeName())); break; | |||||
case 11: exp = new CodeBlocksProjectExporter (project, ValueTree (CodeBlocksProjectExporter ::getValueTreeTypeName (CodeBlocksProjectExporter::windowsTarget)), CodeBlocksProjectExporter::windowsTarget); break; | |||||
case 12: exp = new CodeBlocksProjectExporter (project, ValueTree (CodeBlocksProjectExporter ::getValueTreeTypeName (CodeBlocksProjectExporter::linuxTarget)), CodeBlocksProjectExporter::linuxTarget); break; | |||||
case 9: exp = new AndroidProjectExporter (project, ValueTree (AndroidProjectExporter ::getValueTreeTypeName())); break; | |||||
case 10: exp = new CodeBlocksProjectExporter (project, ValueTree (CodeBlocksProjectExporter ::getValueTreeTypeName (CodeBlocksProjectExporter::windowsTarget)), CodeBlocksProjectExporter::windowsTarget); break; | |||||
case 11: exp = new CodeBlocksProjectExporter (project, ValueTree (CodeBlocksProjectExporter ::getValueTreeTypeName (CodeBlocksProjectExporter::linuxTarget)), CodeBlocksProjectExporter::linuxTarget); break; | |||||
default: jassertfalse; return 0; | default: jassertfalse; return 0; | ||||
} | } | ||||
@@ -130,8 +126,7 @@ ProjectExporter* ProjectExporter::createExporter (Project& project, const ValueT | |||||
if (exp == nullptr) exp = MSVCProjectExporterVC2015 ::createForSettings (project, settings); | if (exp == nullptr) exp = MSVCProjectExporterVC2015 ::createForSettings (project, settings); | ||||
if (exp == nullptr) exp = XCodeProjectExporter ::createForSettings (project, settings); | if (exp == nullptr) exp = XCodeProjectExporter ::createForSettings (project, settings); | ||||
if (exp == nullptr) exp = MakefileProjectExporter ::createForSettings (project, settings); | if (exp == nullptr) exp = MakefileProjectExporter ::createForSettings (project, settings); | ||||
if (exp == nullptr) exp = AndroidStudioProjectExporter ::createForSettings (project, settings); | |||||
if (exp == nullptr) exp = AndroidAntProjectExporter ::createForSettings (project, settings); | |||||
if (exp == nullptr) exp = AndroidProjectExporter ::createForSettings (project, settings); | |||||
if (exp == nullptr) exp = CodeBlocksProjectExporter ::createForSettings (project, settings); | if (exp == nullptr) exp = CodeBlocksProjectExporter ::createForSettings (project, settings); | ||||
jassert (exp != nullptr); | jassert (exp != nullptr); | ||||
@@ -158,7 +153,7 @@ bool ProjectExporter::canProjectBeLaunched (Project* project) | |||||
// (this doesn't currently launch.. not really sure what it would do on linux) | // (this doesn't currently launch.. not really sure what it would do on linux) | ||||
//MakefileProjectExporter::getValueTreeTypeName(), | //MakefileProjectExporter::getValueTreeTypeName(), | ||||
#endif | #endif | ||||
AndroidStudioProjectExporter::getValueTreeTypeName(), | |||||
AndroidProjectExporter::getValueTreeTypeName(), | |||||
nullptr | nullptr | ||||
}; | }; | ||||
@@ -829,6 +824,28 @@ StringPairArray ProjectExporter::BuildConfiguration::getAllPreprocessorDefs() co | |||||
parsePreprocessorDefs (getBuildConfigPreprocessorDefsString())); | parsePreprocessorDefs (getBuildConfigPreprocessorDefsString())); | ||||
} | } | ||||
StringPairArray ProjectExporter::BuildConfiguration::getUniquePreprocessorDefs() const | |||||
{ | |||||
StringPairArray perConfigurationDefs (parsePreprocessorDefs (getBuildConfigPreprocessorDefsString())); | |||||
const StringPairArray globalDefs (project.getPreprocessorDefs()); | |||||
for (int i = 0; i < globalDefs.size(); ++i) | |||||
{ | |||||
String globalKey = globalDefs.getAllKeys()[i]; | |||||
int idx = perConfigurationDefs.getAllKeys().indexOf (globalKey); | |||||
if (idx >= 0) | |||||
{ | |||||
String globalValue = globalDefs.getAllValues()[i]; | |||||
if (globalValue == perConfigurationDefs.getAllValues()[idx]) | |||||
perConfigurationDefs.remove (idx); | |||||
} | |||||
} | |||||
return perConfigurationDefs; | |||||
} | |||||
StringArray ProjectExporter::BuildConfiguration::getHeaderSearchPaths() const | StringArray ProjectExporter::BuildConfiguration::getHeaderSearchPaths() const | ||||
{ | { | ||||
return getSearchPathsFromString (getHeaderSearchPathString()); | return getSearchPathsFromString (getHeaderSearchPathString()); | ||||
@@ -75,7 +75,6 @@ public: | |||||
virtual bool isCodeBlocks() const = 0; | virtual bool isCodeBlocks() const = 0; | ||||
virtual bool isMakefile() const = 0; | virtual bool isMakefile() const = 0; | ||||
virtual bool isAndroidStudio() const = 0; | virtual bool isAndroidStudio() const = 0; | ||||
virtual bool isAndroidAnt() const = 0; | |||||
// operating system targeted by exporter | // operating system targeted by exporter | ||||
virtual bool isAndroid() const = 0; | virtual bool isAndroid() const = 0; | ||||
@@ -241,7 +240,8 @@ public: | |||||
Value getBuildConfigPreprocessorDefs() { return getValue (Ids::defines); } | Value getBuildConfigPreprocessorDefs() { return getValue (Ids::defines); } | ||||
String getBuildConfigPreprocessorDefsString() const { return config [Ids::defines]; } | String getBuildConfigPreprocessorDefsString() const { return config [Ids::defines]; } | ||||
StringPairArray getAllPreprocessorDefs() const; // includes inherited definitions | |||||
StringPairArray getAllPreprocessorDefs() const; // includes inherited definitions | |||||
StringPairArray getUniquePreprocessorDefs() const; // returns pre-processor definitions that are not already in the project pre-processor defs | |||||
Value getHeaderSearchPathValue() { return getValue (Ids::headerPath); } | Value getHeaderSearchPathValue() { return getValue (Ids::headerPath); } | ||||
String getHeaderSearchPathString() const { return config [Ids::headerPath]; } | String getHeaderSearchPathString() const { return config [Ids::headerPath]; } | ||||
@@ -44,7 +44,7 @@ | |||||
#undef KeyPress | #undef KeyPress | ||||
#undef Drawable | #undef Drawable | ||||
#undef Time | #undef Time | ||||
#elif JUCE_ANDROID | |||||
#else | #else | ||||
#if ! (defined (JUCE_SUPPORT_CARBON) || defined (__LP64__)) | #if ! (defined (JUCE_SUPPORT_CARBON) || defined (__LP64__)) | ||||
#define JUCE_SUPPORT_CARBON 1 | #define JUCE_SUPPORT_CARBON 1 | ||||
@@ -274,6 +274,7 @@ private: | |||||
if (hostFilename.startsWith ("Bitwig")) return BitwigStudio; | if (hostFilename.startsWith ("Bitwig")) return BitwigStudio; | ||||
#elif JUCE_IOS | #elif JUCE_IOS | ||||
#elif JUCE_ANDROID | |||||
#else | #else | ||||
#error | #error | ||||
#endif | #endif | ||||
@@ -55,6 +55,7 @@ import android.text.InputType; | |||||
import android.util.DisplayMetrics; | import android.util.DisplayMetrics; | ||||
import android.util.Log; | import android.util.Log; | ||||
import java.lang.Runnable; | import java.lang.Runnable; | ||||
import java.lang.reflect.*; | |||||
import java.util.*; | import java.util.*; | ||||
import java.io.*; | import java.io.*; | ||||
import java.net.URL; | import java.net.URL; | ||||
@@ -62,8 +63,6 @@ import java.net.HttpURLConnection; | |||||
import android.media.AudioManager; | import android.media.AudioManager; | ||||
import android.media.MediaScannerConnection; | import android.media.MediaScannerConnection; | ||||
import android.media.MediaScannerConnection.MediaScannerConnectionClient; | import android.media.MediaScannerConnection.MediaScannerConnectionClient; | ||||
import android.support.v4.content.ContextCompat; | |||||
import android.support.v4.app.ActivityCompat; | |||||
import android.Manifest; | import android.Manifest; | ||||
$$JuceAndroidMidiImports$$ // If you get an error here, you need to re-save your project with the Projucer! | $$JuceAndroidMidiImports$$ // If you get an error here, you need to re-save your project with the Projucer! | ||||
@@ -120,7 +119,7 @@ public class JuceAppActivity extends Activity | |||||
public boolean isPermissionGranted (int permissionID) | public boolean isPermissionGranted (int permissionID) | ||||
{ | { | ||||
return ContextCompat.checkSelfPermission (this, getAndroidPermissionName (permissionID)) == PackageManager.PERMISSION_GRANTED; | |||||
return getApplicationContext().checkCallingOrSelfPermission (getAndroidPermissionName (permissionID)) == PackageManager.PERMISSION_GRANTED; | |||||
} | } | ||||
private Map<Integer, Long> permissionCallbackPtrMap; | private Map<Integer, Long> permissionCallbackPtrMap; | ||||
@@ -129,11 +128,11 @@ public class JuceAppActivity extends Activity | |||||
{ | { | ||||
String permissionName = getAndroidPermissionName (permissionID); | String permissionName = getAndroidPermissionName (permissionID); | ||||
if (ContextCompat.checkSelfPermission (this, permissionName) != PackageManager.PERMISSION_GRANTED) | |||||
if (getApplicationContext().checkCallingOrSelfPermission (permissionName) != PackageManager.PERMISSION_GRANTED) | |||||
{ | { | ||||
// remember callbackPtr, request permissions, and let onRequestPermissionResult call callback asynchronously | // remember callbackPtr, request permissions, and let onRequestPermissionResult call callback asynchronously | ||||
permissionCallbackPtrMap.put (permissionID, ptrToCallback); | permissionCallbackPtrMap.put (permissionID, ptrToCallback); | ||||
ActivityCompat.requestPermissions (this, new String[]{permissionName}, permissionID); | |||||
requestPermissionsCompat (new String[]{permissionName}, permissionID); | |||||
} | } | ||||
else | else | ||||
{ | { | ||||
@@ -299,6 +298,27 @@ public class JuceAppActivity extends Activity | |||||
catch (java.lang.reflect.InvocationTargetException e) {} | catch (java.lang.reflect.InvocationTargetException e) {} | ||||
} | } | ||||
void requestPermissionsCompat (String[] permissions, int requestCode) | |||||
{ | |||||
Method requestPermissionsMethod = null; | |||||
try | |||||
{ | |||||
requestPermissionsMethod = this.getClass().getMethod ("requestPermissions", | |||||
String[].class, int.class); | |||||
} | |||||
catch (SecurityException e) { return; } | |||||
catch (NoSuchMethodException e) { return; } | |||||
if (requestPermissionsMethod == null) return; | |||||
try | |||||
{ | |||||
requestPermissionsMethod.invoke (this, permissions, requestCode); | |||||
} | |||||
catch (java.lang.IllegalArgumentException e) {} | |||||
catch (java.lang.IllegalAccessException e) {} | |||||
catch (java.lang.reflect.InvocationTargetException e) {} | |||||
} | |||||
//============================================================================== | //============================================================================== | ||||
private native void launchApp (String appFile, String appDataDir); | private native void launchApp (String appFile, String appDataDir); | ||||
private native void quitApp(); | private native void quitApp(); | ||||
@@ -735,6 +755,26 @@ public class JuceAppActivity extends Activity | |||||
public void setViewName (String newName) {} | public void setViewName (String newName) {} | ||||
public void setSystemUiVisibilityCompat (int visibility) | |||||
{ | |||||
Method systemUIVisibilityMethod = null; | |||||
try | |||||
{ | |||||
systemUIVisibilityMethod = this.getClass().getMethod ("setSystemUiVisibility", int.class); | |||||
} | |||||
catch (SecurityException e) { return; } | |||||
catch (NoSuchMethodException e) { return; } | |||||
if (systemUIVisibilityMethod == null) return; | |||||
try | |||||
{ | |||||
systemUIVisibilityMethod.invoke (this, visibility); | |||||
} | |||||
catch (java.lang.IllegalArgumentException e) {} | |||||
catch (java.lang.IllegalAccessException e) {} | |||||
catch (java.lang.reflect.InvocationTargetException e) {} | |||||
} | |||||
public boolean isVisible() { return getVisibility() == VISIBLE; } | public boolean isVisible() { return getVisibility() == VISIBLE; } | ||||
public void setVisible (boolean b) { setVisibility (b ? VISIBLE : INVISIBLE); } | public void setVisible (boolean b) { setVisibility (b ? VISIBLE : INVISIBLE); } | ||||
@@ -106,7 +106,7 @@ DECLARE_JNI_CLASS (CanvasMinimal, "android/graphics/Canvas"); | |||||
METHOD (invalidate, "invalidate", "(IIII)V") \ | METHOD (invalidate, "invalidate", "(IIII)V") \ | ||||
METHOD (containsPoint, "containsPoint", "(II)Z") \ | METHOD (containsPoint, "containsPoint", "(II)Z") \ | ||||
METHOD (showKeyboard, "showKeyboard", "(Ljava/lang/String;)V") \ | METHOD (showKeyboard, "showKeyboard", "(Ljava/lang/String;)V") \ | ||||
METHOD (setSystemUiVisibility, "setSystemUiVisibility", "(I)V") \ | |||||
METHOD (setSystemUiVisibility, "setSystemUiVisibilityCompat", "(I)V") \ | |||||
DECLARE_JNI_CLASS (ComponentPeerView, JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView"); | DECLARE_JNI_CLASS (ComponentPeerView, JUCE_ANDROID_ACTIVITY_CLASSPATH "$ComponentPeerView"); | ||||
#undef JNI_CLASS_MEMBERS | #undef JNI_CLASS_MEMBERS | ||||