mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
Reworked and modernised Android exporter and removed old deprecated Android ant exporter
This commit is contained in:
parent
0117aace01
commit
ec0485388d
15 changed files with 1352 additions and 1830 deletions
|
|
@ -404,12 +404,8 @@
|
|||
file="Source/Project/jucer_TreeItemTypes.h"/>
|
||||
</GROUP>
|
||||
<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"
|
||||
resource="0" file="Source/Project Saving/jucer_ProjectExport_CodeBlocks.h"/>
|
||||
<FILE id="lv4d6B" name="jucer_ProjectExport_Make.h" compile="0" resource="0"
|
||||
|
|
|
|||
1271
extras/Projucer/Source/Project Saving/jucer_ProjectExport_Android.h
Normal file
1271
extras/Projucer/Source/Project Saving/jucer_ProjectExport_Android.h
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -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 isMakefile() const override { return false; }
|
||||
bool isAndroidStudio() const override { return false; }
|
||||
bool isAndroidAnt() const override { return false; }
|
||||
|
||||
bool isAndroid() const override { return false; }
|
||||
bool isWindows() const override { return os == windowsTarget; }
|
||||
|
|
|
|||
|
|
@ -536,7 +536,6 @@ public:
|
|||
bool isCodeBlocks() const override { return false; }
|
||||
bool isMakefile() const override { return false; }
|
||||
bool isAndroidStudio() const override { return false; }
|
||||
bool isAndroidAnt() const override { return false; }
|
||||
|
||||
bool isAndroid() const override { return false; }
|
||||
bool isWindows() const override { return true; }
|
||||
|
|
|
|||
|
|
@ -300,7 +300,6 @@ public:
|
|||
bool isCodeBlocks() const override { return false; }
|
||||
bool isMakefile() const override { return true; }
|
||||
bool isAndroidStudio() const override { return false; }
|
||||
bool isAndroidAnt() const override { return false; }
|
||||
|
||||
bool isAndroid() const override { return false; }
|
||||
bool isWindows() const override { return false; }
|
||||
|
|
|
|||
|
|
@ -119,7 +119,6 @@ public:
|
|||
bool isCodeBlocks() const override { return false; }
|
||||
bool isMakefile() const override { return false; }
|
||||
bool isAndroidStudio() const override { return false; }
|
||||
bool isAndroidAnt() const override { return false; }
|
||||
|
||||
bool isAndroid() const override { return false; }
|
||||
bool isWindows() const override { return false; }
|
||||
|
|
|
|||
|
|
@ -29,9 +29,7 @@
|
|||
#include "jucer_ProjectExport_Make.h"
|
||||
#include "jucer_ProjectExport_MSVC.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"
|
||||
|
||||
//==============================================================================
|
||||
|
|
@ -55,8 +53,7 @@ Array<ProjectExporter::ExporterTypeInfo> ProjectExporter::getExporterTypes()
|
|||
addType (types, MSVCProjectExporterVC2008::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, 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::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 7: exp = new MSVCProjectExporterVC2005 (project, ValueTree (MSVCProjectExporterVC2005 ::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;
|
||||
}
|
||||
|
||||
|
|
@ -130,8 +126,7 @@ ProjectExporter* ProjectExporter::createExporter (Project& project, const ValueT
|
|||
if (exp == nullptr) exp = MSVCProjectExporterVC2015 ::createForSettings (project, settings);
|
||||
if (exp == nullptr) exp = XCodeProjectExporter ::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);
|
||||
|
||||
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)
|
||||
//MakefileProjectExporter::getValueTreeTypeName(),
|
||||
#endif
|
||||
AndroidStudioProjectExporter::getValueTreeTypeName(),
|
||||
AndroidProjectExporter::getValueTreeTypeName(),
|
||||
|
||||
nullptr
|
||||
};
|
||||
|
|
@ -829,6 +824,28 @@ StringPairArray ProjectExporter::BuildConfiguration::getAllPreprocessorDefs() co
|
|||
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
|
||||
{
|
||||
return getSearchPathsFromString (getHeaderSearchPathString());
|
||||
|
|
|
|||
|
|
@ -75,7 +75,6 @@ public:
|
|||
virtual bool isCodeBlocks() const = 0;
|
||||
virtual bool isMakefile() const = 0;
|
||||
virtual bool isAndroidStudio() const = 0;
|
||||
virtual bool isAndroidAnt() const = 0;
|
||||
|
||||
// operating system targeted by exporter
|
||||
virtual bool isAndroid() const = 0;
|
||||
|
|
@ -241,7 +240,8 @@ public:
|
|||
|
||||
Value getBuildConfigPreprocessorDefs() { return getValue (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); }
|
||||
String getHeaderSearchPathString() const { return config [Ids::headerPath]; }
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@
|
|||
#undef KeyPress
|
||||
#undef Drawable
|
||||
#undef Time
|
||||
|
||||
#elif JUCE_ANDROID
|
||||
#else
|
||||
#if ! (defined (JUCE_SUPPORT_CARBON) || defined (__LP64__))
|
||||
#define JUCE_SUPPORT_CARBON 1
|
||||
|
|
|
|||
|
|
@ -274,6 +274,7 @@ private:
|
|||
if (hostFilename.startsWith ("Bitwig")) return BitwigStudio;
|
||||
|
||||
#elif JUCE_IOS
|
||||
#elif JUCE_ANDROID
|
||||
#else
|
||||
#error
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ import android.text.InputType;
|
|||
import android.util.DisplayMetrics;
|
||||
import android.util.Log;
|
||||
import java.lang.Runnable;
|
||||
import java.lang.reflect.*;
|
||||
import java.util.*;
|
||||
import java.io.*;
|
||||
import java.net.URL;
|
||||
|
|
@ -62,8 +63,6 @@ import java.net.HttpURLConnection;
|
|||
import android.media.AudioManager;
|
||||
import android.media.MediaScannerConnection;
|
||||
import android.media.MediaScannerConnection.MediaScannerConnectionClient;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.app.ActivityCompat;
|
||||
import android.Manifest;
|
||||
$$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)
|
||||
{
|
||||
return ContextCompat.checkSelfPermission (this, getAndroidPermissionName (permissionID)) == PackageManager.PERMISSION_GRANTED;
|
||||
return getApplicationContext().checkCallingOrSelfPermission (getAndroidPermissionName (permissionID)) == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
|
||||
private Map<Integer, Long> permissionCallbackPtrMap;
|
||||
|
|
@ -129,11 +128,11 @@ public class JuceAppActivity extends Activity
|
|||
{
|
||||
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
|
||||
permissionCallbackPtrMap.put (permissionID, ptrToCallback);
|
||||
ActivityCompat.requestPermissions (this, new String[]{permissionName}, permissionID);
|
||||
requestPermissionsCompat (new String[]{permissionName}, permissionID);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -299,6 +298,27 @@ public class JuceAppActivity extends Activity
|
|||
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 quitApp();
|
||||
|
|
@ -735,6 +755,26 @@ public class JuceAppActivity extends Activity
|
|||
|
||||
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 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 (containsPoint, "containsPoint", "(II)Z") \
|
||||
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");
|
||||
#undef JNI_CLASS_MEMBERS
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue