From ec0485388d53d403e003d4a40e6e9897d36d94d2 Mon Sep 17 00:00:00 2001 From: hogliux Date: Wed, 1 Feb 2017 15:50:23 +0000 Subject: [PATCH] Reworked and modernised Android exporter and removed old deprecated Android ant exporter --- extras/Projucer/Projucer.jucer | 8 +- .../jucer_ProjectExport_Android.h | 1271 +++++++++++++++++ .../jucer_ProjectExport_AndroidAnt.h | 475 ------ .../jucer_ProjectExport_AndroidBase.h | 482 ------- .../jucer_ProjectExport_AndroidStudio.h | 842 ----------- .../jucer_ProjectExport_CodeBlocks.h | 1 - .../Project Saving/jucer_ProjectExport_MSVC.h | 1 - .../Project Saving/jucer_ProjectExport_Make.h | 1 - .../jucer_ProjectExport_XCode.h | 1 - .../Project Saving/jucer_ProjectExporter.cpp | 41 +- .../Project Saving/jucer_ProjectExporter.h | 4 +- .../utility/juce_IncludeSystemHeaders.h | 2 +- .../utility/juce_PluginHostType.h | 1 + .../native/java/JuceAppActivity.java | 50 +- .../native/juce_android_Windowing.cpp | 2 +- 15 files changed, 1352 insertions(+), 1830 deletions(-) create mode 100644 extras/Projucer/Source/Project Saving/jucer_ProjectExport_Android.h delete mode 100644 extras/Projucer/Source/Project Saving/jucer_ProjectExport_AndroidAnt.h delete mode 100644 extras/Projucer/Source/Project Saving/jucer_ProjectExport_AndroidBase.h delete mode 100644 extras/Projucer/Source/Project Saving/jucer_ProjectExport_AndroidStudio.h diff --git a/extras/Projucer/Projucer.jucer b/extras/Projucer/Projucer.jucer index e768d2f0bf..5c2b27625d 100644 --- a/extras/Projucer/Projucer.jucer +++ b/extras/Projucer/Projucer.jucer @@ -404,12 +404,8 @@ file="Source/Project/jucer_TreeItemTypes.h"/> - - - + androidScreenOrientation, androidActivityClass, androidActivitySubClassName, + androidVersionCode, androidMinimumSDK, androidTheme, + androidSharedLibraries, androidStaticLibraries; + + CachedValue androidInternetNeeded, androidMicNeeded, androidBluetoothNeeded; + CachedValue androidOtherPermissions; + + CachedValue androidKeyStore, androidKeyStorePass, androidKeyAlias, androidKeyAliasPass; + + CachedValue gradleVersion, androidPluginVersion, gradleToolchain, buildToolsVersion; + + //============================================================================== + AndroidProjectExporter (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, "10"), + androidTheme (settings, Ids::androidTheme, nullptr), + androidSharedLibraries (settings, Ids::androidSharedLibraries, nullptr, ""), + androidStaticLibraries (settings, Ids::androidStaticLibraries, 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"), + gradleVersion (settings, Ids::gradleVersion, nullptr, "2.14.1"), + androidPluginVersion (settings, Ids::androidPluginVersion, nullptr, "2.2.3"), + gradleToolchain (settings, Ids::gradleToolchain, nullptr, "clang"), + buildToolsVersion (settings, Ids::buildToolsVersion, nullptr, "25.0.2"), + AndroidExecutable (findAndroidExecutable()) + { + initialiseDependencyPathValues(); + name = getName(); + + if (getTargetLocationString().isEmpty()) + getTargetLocationValue() = getDefaultBuildsRootFolder() + "Android"; + } + + //============================================================================== + void createToolchainExporterProperties (PropertyListBuilder& props) + { + props.add (new TextWithDefaultPropertyComponent (gradleVersion, "gradle version", 32), + "The version of gradle that is used to build this app (2.14.1 is fine for JUCE)"); + + props.add (new TextWithDefaultPropertyComponent (androidPluginVersion, "android plug-in version", 32), + "The version of the android build plugin for gradle that is used to build this app"); + + static const char* toolchains[] = { "clang", "gcc", nullptr }; + + props.add (new ChoicePropertyComponent (gradleToolchain.getPropertyAsValue(), "NDK Toolchain", StringArray (toolchains), Array (toolchains)), + "The toolchain that gradle should invoke for NDK compilation (variable model.android.ndk.tooclhain in app/build.gradle)"); + + props.add (new TextWithDefaultPropertyComponent (buildToolsVersion, "Android build tools version", 32), + "The Android build tools version that should use to build this app"); + } + + void createLibraryModuleExporterProperties (PropertyListBuilder& props) + { + props.add (new TextPropertyComponent (androidStaticLibraries.getPropertyAsValue(), "Import static library modules", 8192, true), + "Comma or whitespace delimited list of static libraries (.a) defined in NDK_MODULE_PATH."); + + props.add (new TextPropertyComponent (androidSharedLibraries.getPropertyAsValue(), "Import shared library modules", 8192, true), + "Comma or whitespace delimited list of shared libraries (.so) defined in NDK_MODULE_PATH."); + } + + //============================================================================== + bool canLaunchProject() override + { + return AndroidExecutable.exists(); + } + + bool launchProject() override + { + if (! AndroidExecutable.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 AndroidExecutable.startAsProcess ("\"" + targetFolder.getFullPathName() + "\""); + } + + //============================================================================== + void create (const OwnedArray& modules) const override + { + const File targetFolder (getTargetFolder()); + const File appFolder (targetFolder.getChildFile (isLibrary() ? "lib" : "app")); + + removeOldFiles (targetFolder); + + { + const String package (getActivityClassPackage()); + const String path (package.replaceCharacter ('.', File::separator)); + const File javaTarget (targetFolder.getChildFile ("app/src/main/java").getChildFile (path)); + + if (! isLibrary()) + copyActivityJavaFiles (modules, javaTarget, package); + } + + writeFile (targetFolder, "settings.gradle", isLibrary() ? "include ':lib'" : "include ':app'"); + writeFile (targetFolder, "build.gradle", getProjectBuildGradleFileContent()); + writeFile (appFolder, "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 (appFolder); + + if (! isLibrary()) + { + writeStringsXML (targetFolder); + writeAppIcons (targetFolder); + } + + writeCmakeFile (appFolder.getChildFile ("CMakeLists.txt")); + } + + 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 (binarySize)); + overwriteFileIfDifferentOrThrow (gradleProjectFolder.getChildFile (filePath), outStream); + } + + //============================================================================== + static File findAndroidExecutable() + { + #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 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() = ""; + } + } + + 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). Leave empty to build for all possible android archiftectures."); + } + + String getProductFlavourNameIdentifier() const + { + return getName().toLowerCase().replaceCharacter (L' ', L'_') + String ("_"); + } + + String getProductFlavourCMakeIdentifier() const + { + return getName().toUpperCase().replaceCharacter (L' ', L'_'); + } + }; + + BuildConfiguration::Ptr createBuildConfig (const ValueTree& v) const override + { + return new AndroidBuildConfiguration (project, v, *this); + } + +private: + void writeCmakeFile (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; + + mo << "cmake_minimum_required(VERSION 3.4.1)" << newLine << newLine; + + if (! isLibrary()) + mo << "SET(BINARY_NAME \"juce_jni\")" << newLine << newLine; + + { + StringArray projectDefines (getEscapedPreprocessorDefs (getProjectPreprocessorDefs())); + if (projectDefines.size() > 0) + mo << "add_definitions(" << projectDefines.joinIntoString (" ") << ")" << newLine << newLine; + } + + writeCmakePathLines (mo, "", "include_directories( AFTER", extraSearchPaths); + + const String& cfgExtraLinkerFlags = getExtraLinkerFlagsString(); + if (cfgExtraLinkerFlags.isNotEmpty()) + { + mo << "SET( JUCE_LDFLAGS \"" << cfgExtraLinkerFlags.replace ("\"", "\\\"") << "\")" << newLine; + mo << "SET( CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} ${JUCE_LDFLAGS}\")" << newLine << newLine; + } + + if (getNumConfigurations() > 0) + { + bool first = true; + + for (ConstConfigIterator config (*this); config.next();) + { + const auto& cfg = dynamic_cast (*config); + const StringArray& libSearchPaths = cfg.getLibrarySearchPaths(); + const StringPairArray& cfgDefines = getConfigPreprocessorDefs (cfg); + const StringArray& cfgHeaderPaths = cfg.getHeaderSearchPaths(); + const StringArray& cfgLibraryPaths = cfg.getLibrarySearchPaths(); + + if (! isLibrary() && libSearchPaths.size() == 0 && cfgDefines.size() == 0 + && cfgHeaderPaths.size() == 0 && cfgLibraryPaths.size() == 0) + continue; + + mo << (first ? "IF" : "ELSEIF") << "(JUCE_BUILD_CONFIGFURATION MATCHES \"" << cfg.getProductFlavourCMakeIdentifier() <<"\")" << newLine; + + if (isLibrary()) + mo << " SET(BINARY_NAME \"" << getNativeModuleBinaryName (cfg) << "\")" << newLine; + + writeCmakePathLines (mo, " ", "link_directories(", libSearchPaths); + + if (cfgDefines.size() > 0) + mo << " add_definitions(" << getEscapedPreprocessorDefs (cfgDefines).joinIntoString (" ") << ")" << newLine; + + writeCmakePathLines (mo, " ", "include_directories( AFTER", cfgHeaderPaths); + writeCmakePathLines (mo, " ", "link_directories(", cfgLibraryPaths); + + first = false; + } + + if (! first) + { + const auto& cfg = dynamic_cast (* getConfiguration(0)); + mo << "ELSE(JUCE_BUILD_CONFIGFURATION MATCHES \"" << cfg.getProductFlavourCMakeIdentifier() <<"\")" << newLine; + mo << " MESSAGE( FATAL_ERROR \"No matching build-configuration found.\" )" << newLine; + mo << "ENDIF(JUCE_BUILD_CONFIGFURATION MATCHES \"" << cfg.getProductFlavourCMakeIdentifier() <<"\")" << newLine << newLine; + + } + } + + Array excludeFromBuild; + + mo << "add_library( ${BINARY_NAME}" << newLine; + mo << newLine; + mo << " SHARED" << newLine; + mo << newLine; + addCompileUnits (mo, excludeFromBuild); + mo << ")" << newLine << newLine; + + if (excludeFromBuild.size() > 0) + { + for (auto& exclude : excludeFromBuild) + mo << "set_source_files_properties(\"" << exclude.toUnixStyle() << "\" PROPERTIES HEADER_FILE_ONLY TRUE)" << newLine; + + mo << newLine; + } + + const StringArray& libraries = getProjectLibraries(); + + if (libraries.size() > 0) + { + for (auto& lib : libraries) + mo << "find_library(" << lib.toLowerCase().replaceCharacter (L' ', L'_') << " \"" << lib << "\")" << newLine; + + mo << newLine; + } + + mo << "target_link_libraries( ${BINARY_NAME}"; + if (libraries.size() > 0) + { + mo << newLine << newLine; + + for (auto& lib : libraries) + mo << " ${" << lib.toLowerCase().replaceCharacter (L' ', L'_') << "}" << newLine; + } + mo << ")" << newLine; + + overwriteFileIfDifferentOrThrow (file, mo); + } + + //============================================================================== + String getProjectBuildGradleFileContent() const + { + MemoryOutputStream mo; + + mo << "buildscript {" << newLine; + mo << " repositories {" << newLine; + mo << " jcenter()" << newLine; + mo << " }" << newLine; + mo << " dependencies {" << newLine; + mo << " classpath 'com.android.tools.build:gradle:" << androidPluginVersion.get() << "'" << newLine; + mo << " }" << newLine; + mo << "}" << newLine; + mo << "" << newLine; + mo << "allprojects {" << newLine; + mo << " repositories {" << newLine; + mo << " jcenter()" << newLine; + mo << " }" << newLine; + mo << "}" << newLine; + + return mo.toString(); + } + + //============================================================================== + String getAppBuildGradleFileContent () const + { + MemoryOutputStream mo; + mo << "apply plugin: 'com.android." << (isLibrary() ? "library" : "application") << "'" << newLine << newLine; + + mo << "android {" << newLine; + mo << " compileSdkVersion " << androidMinimumSDK.get().getIntValue() << newLine; + mo << " buildToolsVersion \"" << buildToolsVersion.get() << "\"" << newLine; + mo << " externalNativeBuild {" << newLine; + mo << " cmake {" << newLine; + mo << " path \"CMakeLists.txt\"" << newLine; + mo << " }" << newLine; + mo << " }" << newLine; + + mo << getAndroidSigningConfig() << newLine; + mo << getAndroidDefaultConfig() << newLine; + mo << getAndroidBuildTypes() << newLine; + mo << getAndroidProductFlavours() << newLine; + mo << getAndroidVariantFilter() << newLine; + + + mo << "}" << newLine; + + return mo.toString(); + } + + String getAndroidProductFlavours() const + { + MemoryOutputStream mo; + + mo << " productFlavors {" << newLine; + + for (ConstConfigIterator config (*this); config.next();) + { + const auto& cfg = dynamic_cast (*config); + + mo << " " << cfg.getProductFlavourNameIdentifier() << " {" << newLine; + + if (cfg.getArchitectures().isNotEmpty()) + { + mo << " ndk {" << newLine; + mo << " abiFilters " << toGradleList (StringArray::fromTokens (cfg.getArchitectures(), " ", "")) << newLine; + mo << " }" << newLine; + } + + mo << " externalNativeBuild {" << newLine; + mo << " cmake {" << newLine; + mo << " arguments \"-DJUCE_BUILD_CONFIGFURATION=" << cfg.getProductFlavourCMakeIdentifier() << "\"" << newLine; + mo << " cFlags \"-O" << cfg.getGCCOptimisationFlag() << "\"" << newLine; + mo << " cppFlags \"-O" << cfg.getGCCOptimisationFlag() << "\"" << newLine; + mo << " }" << newLine; + mo << " }" << newLine; + mo << " }" << newLine; + } + + mo << " }" << newLine; + + return mo.toString(); + } + + String getAndroidSigningConfig() const + { + MemoryOutputStream mo; + + String keyStoreFilePath = androidKeyStore.get().replace ("${user.home}", "${System.properties['user.home']}") + .replace ("/", "${File.separator}"); + + mo << " signingConfigs {" << newLine; + mo << " release {" << newLine; + mo << " storeFile file(\"" << keyStoreFilePath << "\")" << newLine; + mo << " storePassword \"" << androidKeyStorePass.get() << "\"" << newLine; + mo << " keyAlias \"" << androidKeyAlias.get() << "\"" << newLine; + mo << " keyPassword \"" << androidKeyAliasPass.get() << "\"" << newLine; + mo << " storeType \"jks\"" << newLine; + mo << " }" << newLine; + mo << " }" << newLine; + + return mo.toString(); + } + + String getAndroidDefaultConfig() const + { + const String bundleIdentifier = project.getBundleIdentifier().toString().toLowerCase(); + const StringArray& cmakeDefs = getCmakeDefinitions(); + const StringArray& cFlags = getProjectCompilerFlags(); + const StringArray& cxxFlags = getProjectCxxCompilerFlags(); + const int minSdkVersion = androidMinimumSDK.get().getIntValue(); + + MemoryOutputStream mo; + + mo << " defaultConfig {" << newLine; + + if (! isLibrary()) + mo << " applicationId \"" << bundleIdentifier << "\"" << newLine; + + mo << " minSdkVersion " << minSdkVersion << newLine; + mo << " targetSdkVersion " << minSdkVersion << newLine; + + mo << " externalNativeBuild {" << newLine; + mo << " cmake {" << newLine; + mo << " arguments " << cmakeDefs.joinIntoString (", ") << newLine; + + if (cFlags.size() > 0) + mo << " cFlags " << cFlags.joinIntoString (", ") << newLine; + + if (cxxFlags.size() > 0) + mo << " cppFlags " << cxxFlags.joinIntoString (", ") << newLine; + + mo << " }" << newLine; + mo << " }" << newLine; + mo << " }" << newLine; + + return mo.toString(); + } + + String getAndroidBuildTypes() const + { + MemoryOutputStream mo; + + mo << " buildTypes {" << newLine; + + int numDebugConfigs = 0; + const int numConfigs = getNumConfigurations(); + for (int i = 0; i < numConfigs; ++i) + { + auto config = getConfiguration(i); + + if (config->isDebug()) numDebugConfigs++; + + if (numDebugConfigs > 1 || ((numConfigs - numDebugConfigs) > 1)) + continue; + + mo << " " << (config->isDebug() ? "debug" : "release") << " {" << newLine; + mo << " initWith " << (config->isDebug() ? "debug" : "release") << newLine; + mo << " debuggable " << (config->isDebug() ? "true" : "false") << newLine; + mo << " jniDebuggable " << (config->isDebug() ? "true" : "false") << newLine; + + if (! config->isDebug()) + mo << " signingConfig signingConfigs.release" << newLine; + + mo << " }" << newLine; + } + mo << " }" << newLine; + + return mo.toString(); + } + + String getAndroidVariantFilter() const + { + MemoryOutputStream mo; + + mo << " variantFilter { variant ->" << newLine; + mo << " def names = variant.flavors*.name" << newLine; + + for (ConstConfigIterator config (*this); config.next();) + { + const auto& cfg = dynamic_cast (*config); + + mo << " if (names.contains (\"" << cfg.getProductFlavourNameIdentifier() << "\")" << newLine; + mo << " && variant.buildType.name != \"" << (cfg.isDebug() ? "debug" : "release") << "\") {" << newLine; + mo << " setIgnore(true)" << newLine; + mo << " }" << newLine; + } + + mo << " }" << newLine; + + return mo.toString(); + } + + //============================================================================== + 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 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 (orientationValues)), + "The screen orientations that this app should support"); + + props.add (new TextWithDefaultPropertyComponent (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 (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 (androidMinimumSDK, "Minimum SDK version", 32), + "The number of the minimum version of the Android SDK that the app requires"); + } + + //============================================================================== + 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."); + } + + //============================================================================== + void createCodeSigningExporterProperties (PropertyListBuilder& props) + { + props.add (new TextWithDefaultPropertyComponent (androidKeyStore, "Key Signing: key.store", 2048), + "The key.store value, used when signing the release package."); + + props.add (new TextWithDefaultPropertyComponent (androidKeyStorePass, "Key Signing: key.store.password", 2048), + "The key.store password, used when signing the release package."); + + props.add (new TextWithDefaultPropertyComponent (androidKeyAlias, "Key Signing: key.alias", 2048), + "The key.alias value, used when signing the release package."); + + props.add (new TextWithDefaultPropertyComponent (androidKeyAliasPass, "Key Signing: key.alias.password", 2048), + "The key.alias password, used when signing the release 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& 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& modules) + { + for (int i = modules.size(); --i >= 0;) + if (modules.getUnchecked(i)->getID() == "juce_core") + return modules.getUnchecked(i); + + return nullptr; + } + + //============================================================================== + String getNativeModuleBinaryName (const AndroidBuildConfiguration& config) const + { + return (isLibrary() ? File::createLegalFileName (config.getTargetBinaryNameString().trim()) : "juce_jni"); + } + + 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 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 manifest (createManifestXML()); + + writeXmlOrThrow (*manifest, folder.getChildFile ("src/main/AndroidManifest.xml"), "utf-8", 100, true); + } + + 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 bigIcon (getBigIcon()); + ScopedPointer 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())); + } + } + + 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); + } + + //============================================================================== + void addCompileUnits (const Project::Item& projectItem, MemoryOutputStream& mo, Array& excludeFromBuild) const + { + if (projectItem.isGroup()) + { + for (int i = 0; i < projectItem.getNumChildren(); ++i) + addCompileUnits (projectItem.getChild(i), mo, excludeFromBuild); + } + else if (projectItem.shouldBeAddedToTargetProject()) + { + const RelativePath file (projectItem.getFile(), getTargetFolder().getChildFile ("app"), RelativePath::buildTargetFolder); + const ProjectType::Target::Type targetType = getProject().getTargetTypeFromFilePath (projectItem.getFile(), true); + + mo << " \"" << file.toUnixStyle() << "\"" << newLine; + + if ((! projectItem.shouldBeCompiled()) || (! shouldFileBeCompiledByDefault (file)) + || (getProject().getProjectType().isAudioPlugin() + && targetType != ProjectType::Target::SharedCodeTarget + && targetType != ProjectType::Target::StandalonePlugIn)) + excludeFromBuild.add (file); + } + } + + void addCompileUnits (MemoryOutputStream& mo, Array& excludeFromBuild) const + { + for (int i = 0; i < getAllGroups().size(); ++i) + addCompileUnits (getAllGroups().getReference(i), mo, excludeFromBuild); + } + + //============================================================================== + StringArray getCmakeDefinitions() const + { + const String toolchain = gradleToolchain.get(); + const bool isClang = (toolchain == "clang"); + + StringArray cmakeArgs; + + cmakeArgs.add ("\"-DANDROID_TOOLCHAIN=" + toolchain + "\""); + cmakeArgs.add ("\"-DANDROID_PLATFORM=" + getAppPlatform() + "\""); + cmakeArgs.add (String ("\"-DANDROID_STL=") + (isClang ? "c++_static" : "gnustl_static") + "\""); + cmakeArgs.add ("\"-DANDROID_CPP_FEATURES=exceptions rtti\""); + cmakeArgs.add ("\"-DANDROID_ARM_MODE=arm\""); + cmakeArgs.add ("\"-DANDROID_ARM_NEON=TRUE\""); + + return cmakeArgs; + } + + //============================================================================== + StringArray getAndroidCompilerFlags() const + { + StringArray cFlags; + cFlags.add ("\"-fsigned-char\""); + + return cFlags; + } + + StringArray getAndroidCxxCompilerFlags() const + { + StringArray cxxFlags (getAndroidCompilerFlags()); + cxxFlags.add ("\"-std=gnu++11\""); + + return cxxFlags; + } + + StringArray getProjectCompilerFlags() const + { + StringArray cFlags (getAndroidCompilerFlags()); + + cFlags.addArray (getEscapedFlags (StringArray::fromTokens (getExtraCompilerFlagsString(), true))); + return cFlags; + } + + StringArray getProjectCxxCompilerFlags() const + { + StringArray cxxFlags (getAndroidCxxCompilerFlags()); + + cxxFlags.addArray (getEscapedFlags (StringArray::fromTokens (getExtraCompilerFlagsString(), true))); + return cxxFlags; + } + + //============================================================================== + StringPairArray getAndroidPreprocessorDefs() 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() + "\""); + + return defines; + } + + StringPairArray getProjectPreprocessorDefs() const + { + StringPairArray defines (getAndroidPreprocessorDefs()); + + return mergePreprocessorDefs (defines, getProject().getPreprocessorDefs()); + } + + StringPairArray getConfigPreprocessorDefs (const BuildConfiguration& config) const + { + StringPairArray cfgDefines (config.getUniquePreprocessorDefs()); + + if (config.isDebug()) + { + cfgDefines.set ("DEBUG", "1"); + cfgDefines.set ("_DEBUG", "1"); + } + else + { + cfgDefines.set ("NDEBUG", "1"); + } + + return cfgDefines; + } + + //============================================================================== + StringArray getAndroidLibraries() const + { + StringArray libraries; + + libraries.add ("log"); + libraries.add ("android"); + libraries.add (androidMinimumSDK.get().getIntValue() >= 18 ? "GLESv3" : "GLESv2"); + libraries.add ("EGL"); + + return libraries; + } + + StringArray getProjectLibraries() const + { + StringArray libraries (getAndroidLibraries()); + libraries.addArray (StringArray::fromLines (getExternalLibrariesString())); + + return libraries; + } + + //============================================================================== + StringArray getHeaderSearchPaths (const BuildConfiguration& config) const + { + StringArray paths (extraSearchPaths); + paths.addArray (config.getHeaderSearchPaths()); + paths = getCleanedStringArray (paths); + return paths; + } + + //============================================================================== + String escapeDirectoryForCmake (const String& path) const + { + RelativePath relative = + RelativePath (path, RelativePath::buildTargetFolder) + .rebased (getTargetFolder(), getTargetFolder().getChildFile ("app"), RelativePath::buildTargetFolder); + + return relative.toUnixStyle(); + } + + void writeCmakePathLines (MemoryOutputStream& mo, const String& prefix, const String& firstLine, const StringArray& paths) const + { + if (paths.size() > 0) + { + mo << prefix << firstLine << newLine; + + for (auto& path : paths) + mo << prefix << " \"" << escapeDirectoryForCmake (path) << "\"" << newLine; + + mo << prefix << ")" << newLine << newLine; + } + } + + static StringArray getEscapedPreprocessorDefs (const StringPairArray& defs) + { + StringArray escapedDefs; + + for (int i = 0; i < defs.size(); ++i) + { + String escaped ("\"-D" + defs.getAllKeys()[i]); + String value = defs.getAllValues()[i]; + + if (value.isNotEmpty()) + { + value = value.replace ("\"", "\\\""); + if (value.containsChar (L' ')) + value = "\\\"" + value + "\\\""; + + escaped += ("=" + value + "\""); + } + + escapedDefs.add (escaped); + } + + return escapedDefs; + } + + static StringArray getEscapedFlags (const StringArray& flags) + { + StringArray escaped; + + for (auto& flag : flags) + escaped.add ("\"" + flag + "\""); + + return escaped; + } + + //============================================================================== + 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()); + + if (! isLibrary()) + { + XmlElement* screens = manifest->createNewChildElement ("supports-screens"); + screens->setAttribute ("android:smallScreens", "true"); + screens->setAttribute ("android:normalScreens", "true"); + screens->setAttribute ("android:largeScreens", "true"); + screens->setAttribute ("android:anyDensity", "true"); + } + + 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", (androidMinimumSDK.get().getIntValue() >= 18 ? "0x00030000" : "0x00020000")); + feature->setAttribute ("android:required", "true"); + } + + if (! isLibrary()) + { + XmlElement* app = manifest->createNewChildElement ("application"); + app->setAttribute ("android:label", "@string/app_name"); + + if (androidTheme.get().isNotEmpty()) + app->setAttribute ("android:theme", androidTheme.get()); + + { + ScopedPointer 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"); + + String configChanges ("keyboardHidden|orientation"); + if (androidMinimumSDK.get().getIntValue() >= 13) + configChanges += "|screenSize"; + + act->setAttribute ("android:configChanges", configChanges); + 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; + } + + 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); + } + + //============================================================================== + bool isLibrary() const + { + return getProject().getProjectType().isDynamicLibrary() + || getProject().getProjectType().isStaticLibrary(); + } + + static String toGradleList (const StringArray& array) + { + StringArray escapedArray; + + for (auto element : array) + escapedArray.add ("\"" + element.replace ("\\", "\\\\").replace ("\"", "\\\"") + "\""); + + return escapedArray.joinIntoString (", "); + } + + //============================================================================== + Value sdkPath, ndkPath; + const File AndroidExecutable; + + JUCE_DECLARE_NON_COPYABLE (AndroidProjectExporter) +}; + +ProjectExporter* createAndroidExporter (Project& p, const ValueTree& t) +{ + return new AndroidProjectExporter (p, t); +} + +ProjectExporter* createAndroidExporterForSetting (Project& p, const ValueTree& t) +{ + return AndroidProjectExporter::createForSettings (p, t); +} diff --git a/extras/Projucer/Source/Project Saving/jucer_ProjectExport_AndroidAnt.h b/extras/Projucer/Source/Project Saving/jucer_ProjectExport_AndroidAnt.h deleted file mode 100644 index 5fa6efc61a..0000000000 --- a/extras/Projucer/Source/Project Saving/jucer_ProjectExport_AndroidAnt.h +++ /dev/null @@ -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& 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 manifest (createManifestXML()); - writeXmlOrThrow (*manifest, target.getChildFile ("AndroidManifest.xml"), "utf-8", 100, true); - } - - writeApplicationMk (jniFolder.getChildFile ("Application.mk")); - writeAndroidMk (jniFolder.getChildFile ("Android.mk")); - - { - ScopedPointer 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 (true) << newLine - << "else" << newLine - << " APP_ABI := " << getABIs (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 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& 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 (*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 (*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) -}; diff --git a/extras/Projucer/Source/Project Saving/jucer_ProjectExport_AndroidBase.h b/extras/Projucer/Source/Project Saving/jucer_ProjectExport_AndroidBase.h deleted file mode 100644 index 2ca853d335..0000000000 --- a/extras/Projucer/Source/Project Saving/jucer_ProjectExport_AndroidBase.h +++ /dev/null @@ -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& 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 androidScreenOrientation, androidActivityClass, androidActivitySubClassName, - androidVersionCode, androidMinimumSDK, androidTheme; - - CachedValue androidInternetNeeded, androidMicNeeded, androidBluetoothNeeded; - CachedValue androidOtherPermissions; - - CachedValue 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 (orientationValues)), - "The screen orientations that this app should support"); - - props.add (new TextWithDefaultPropertyComponent (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 (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 (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 (androidKeyStore, "Key Signing: key.store", 2048), - "The key.store value, used when signing the package."); - - props.add (new TextWithDefaultPropertyComponent (androidKeyStorePass, "Key Signing: key.store.password", 2048), - "The key.store password, used when signing the package."); - - props.add (new TextWithDefaultPropertyComponent (androidKeyAlias, "Key Signing: key.alias", 2048), - "The key.alias value, used when signing the package."); - - props.add (new TextWithDefaultPropertyComponent (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& 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& 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 - void findAllProjectItemsWithPredicate (const Project::Item& projectItem, Array& 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 bigIcon (getBigIcon()); - ScopedPointer 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 - String getABIs (bool forDebug) const - { - for (ConstConfigIterator config (*this); config.next();) - { - const BuildConfigType& androidConfig = dynamic_cast (*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 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) -}; diff --git a/extras/Projucer/Source/Project Saving/jucer_ProjectExport_AndroidStudio.h b/extras/Projucer/Source/Project Saving/jucer_ProjectExport_AndroidStudio.h deleted file mode 100644 index 1081ae6959..0000000000 --- a/extras/Projucer/Source/Project Saving/jucer_ProjectExport_AndroidStudio.h +++ /dev/null @@ -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 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 (gradleVersion, "gradle version", 32), - "The version of gradle that Android Studio should use to build this app"); - - props.add (new TextWithDefaultPropertyComponent (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 (toolchains)), - "The toolchain that gradle should invoke for NDK compilation (variable model.android.ndk.tooclhain in app/build.gradle)"); - - props.add (new TextWithDefaultPropertyComponent (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& 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 (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& 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 - 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 - 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 - void add (Arg1 arg1) - { - children.add (new GradleType (arg1)); - } - - template - 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 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 ("jcenter()"); - return repositories; - } - - GradleObject* getGradleDependencies() const - { - auto dependencies = new GradleObject ("dependencies"); - - dependencies->add ("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 ("compile \"com.android.support:support-v4:+\""); - return dependencies.toString(); - } - - //============================================================================== - GradleObject* getAndroidObject() const - { - auto android = new GradleObject ("android"); - - android->add ("compileSdkVersion", androidMinimumSDK.get().getIntValue()); - android->add ("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 ("applicationId", bundleIdentifier); - defaultConfig->add ("minSdkVersion.apiLevel", minSdkVersion); - defaultConfig->add ("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 ("moduleName", "juce_jni"); - ndkSettings->add ("toolchain", toolchain); - ndkSettings->add ("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 (cppFlags[i]); - } - - void addNdkPreprocessorDefines (GradleObject* ndk) const - { - const auto& defines = getAllPreprocessorDefs(); - - for (int i = 0; i < defines.size(); ++i) - ndk->add ( 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 (path); - } - - Array getAllCppFilesToBeIncludedWithPath() const - { - Array 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 (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 (lib); - } - - GradleObject* getAndroidSources() const - { - auto source = new GradleObject ("source"); // app source folder - source->add ("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 ("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 ("debuggable", true); - ndkSettings->add ("-g"); - ndkSettings->add ("DEBUG", "1"); - ndkSettings->add ("_DEBUG", "1"); - } - else - { - ndkSettings->add ("NDEBUG", "1"); - } - - ndkSettings->add ("-O" + config.getGCCOptimisationFlag()); - - for (const auto& path : getHeaderSearchPaths (config)) - ndkSettings->add (path); - - for (const auto& path : config.getLibrarySearchPaths()) - ndkSettings->add (path); - - ndkSettings->add ("JUCE_ANDROID", "1"); - ndkSettings->add ("JUCE_ANDROID_API_VERSION", androidMinimumSDK.get()); - ndkSettings->add ("JUCE_ANDROID_ACTIVITY_CLASSNAME", getJNIActivityClassName().replaceCharacter ('/', '_')); - ndkSettings->add ("JUCE_ANDROID_ACTIVITY_CLASSPATH","\\\"" + androidActivityClass.get().replaceCharacter('.', '/') + "\\\""); - - const auto defines = config.getAllPreprocessorDefs(); - for (int i = 0; i < defines.size(); ++i) - ndkSettings->add (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 ("storeFile", androidKeyStore.get()); - releaseConfig->add ("storePassword", androidKeyStorePass.get()); - releaseConfig->add ("keyAlias", androidKeyAlias.get()); - releaseConfig->add ("keyPassword", androidKeyAliasPass.get()); - releaseConfig->add ("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 (true), " ", "")); - architectures.mergeArray (StringArray::fromTokens (getABIs (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 ("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 manifest (createManifestXML()); - - writeXmlOrThrow (*manifest, folder.getChildFile ("app/src/main/AndroidManifest.xml"), "utf-8", 100, true); - } - - //============================================================================== - const File androidStudioExecutable; - - JUCE_DECLARE_NON_COPYABLE (AndroidStudioProjectExporter) -}; diff --git a/extras/Projucer/Source/Project Saving/jucer_ProjectExport_CodeBlocks.h b/extras/Projucer/Source/Project Saving/jucer_ProjectExport_CodeBlocks.h index f125e6d04a..44c191fa6c 100644 --- a/extras/Projucer/Source/Project Saving/jucer_ProjectExport_CodeBlocks.h +++ b/extras/Projucer/Source/Project Saving/jucer_ProjectExport_CodeBlocks.h @@ -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; } diff --git a/extras/Projucer/Source/Project Saving/jucer_ProjectExport_MSVC.h b/extras/Projucer/Source/Project Saving/jucer_ProjectExport_MSVC.h index 76e12497ca..7eb96f8a57 100644 --- a/extras/Projucer/Source/Project Saving/jucer_ProjectExport_MSVC.h +++ b/extras/Projucer/Source/Project Saving/jucer_ProjectExport_MSVC.h @@ -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; } diff --git a/extras/Projucer/Source/Project Saving/jucer_ProjectExport_Make.h b/extras/Projucer/Source/Project Saving/jucer_ProjectExport_Make.h index 502b171d22..11aaab54a2 100644 --- a/extras/Projucer/Source/Project Saving/jucer_ProjectExport_Make.h +++ b/extras/Projucer/Source/Project Saving/jucer_ProjectExport_Make.h @@ -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; } diff --git a/extras/Projucer/Source/Project Saving/jucer_ProjectExport_XCode.h b/extras/Projucer/Source/Project Saving/jucer_ProjectExport_XCode.h index 0ea3822cc9..d64f4211e6 100644 --- a/extras/Projucer/Source/Project Saving/jucer_ProjectExport_XCode.h +++ b/extras/Projucer/Source/Project Saving/jucer_ProjectExport_XCode.h @@ -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; } diff --git a/extras/Projucer/Source/Project Saving/jucer_ProjectExporter.cpp b/extras/Projucer/Source/Project Saving/jucer_ProjectExporter.cpp index 7acac0d3bc..9279b13a50 100644 --- a/extras/Projucer/Source/Project Saving/jucer_ProjectExporter.cpp +++ b/extras/Projucer/Source/Project Saving/jucer_ProjectExporter.cpp @@ -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::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()); diff --git a/extras/Projucer/Source/Project Saving/jucer_ProjectExporter.h b/extras/Projucer/Source/Project Saving/jucer_ProjectExporter.h index 34eb31f4ee..4bc582d3a4 100644 --- a/extras/Projucer/Source/Project Saving/jucer_ProjectExporter.h +++ b/extras/Projucer/Source/Project Saving/jucer_ProjectExporter.h @@ -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]; } diff --git a/modules/juce_audio_plugin_client/utility/juce_IncludeSystemHeaders.h b/modules/juce_audio_plugin_client/utility/juce_IncludeSystemHeaders.h index f59b90eccb..73a8a6dd27 100644 --- a/modules/juce_audio_plugin_client/utility/juce_IncludeSystemHeaders.h +++ b/modules/juce_audio_plugin_client/utility/juce_IncludeSystemHeaders.h @@ -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 diff --git a/modules/juce_audio_plugin_client/utility/juce_PluginHostType.h b/modules/juce_audio_plugin_client/utility/juce_PluginHostType.h index ebca44b1a7..21e33561d6 100644 --- a/modules/juce_audio_plugin_client/utility/juce_PluginHostType.h +++ b/modules/juce_audio_plugin_client/utility/juce_PluginHostType.h @@ -274,6 +274,7 @@ private: if (hostFilename.startsWith ("Bitwig")) return BitwigStudio; #elif JUCE_IOS + #elif JUCE_ANDROID #else #error #endif diff --git a/modules/juce_core/native/java/JuceAppActivity.java b/modules/juce_core/native/java/JuceAppActivity.java index 46337f2708..632784a6e2 100644 --- a/modules/juce_core/native/java/JuceAppActivity.java +++ b/modules/juce_core/native/java/JuceAppActivity.java @@ -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 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); } diff --git a/modules/juce_gui_basics/native/juce_android_Windowing.cpp b/modules/juce_gui_basics/native/juce_android_Windowing.cpp index 09bc0674de..c1efaf2853 100644 --- a/modules/juce_gui_basics/native/juce_android_Windowing.cpp +++ b/modules/juce_gui_basics/native/juce_android_Windowing.cpp @@ -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