The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

2033 lines
94KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #pragma once
  20. //==============================================================================
  21. class AndroidProjectExporter : public ProjectExporter
  22. {
  23. public:
  24. //==============================================================================
  25. bool isXcode() const override { return false; }
  26. bool isVisualStudio() const override { return false; }
  27. bool isCodeBlocks() const override { return false; }
  28. bool isMakefile() const override { return false; }
  29. bool isAndroidStudio() const override { return true; }
  30. bool isCLion() const override { return false; }
  31. bool isAndroid() const override { return true; }
  32. bool isWindows() const override { return false; }
  33. bool isLinux() const override { return false; }
  34. bool isOSX() const override { return false; }
  35. bool isiOS() const override { return false; }
  36. bool usesMMFiles() const override { return false; }
  37. bool canCopeWithDuplicateFiles() override { return false; }
  38. bool supportsUserDefinedConfigurations() const override { return true; }
  39. bool supportsTargetType (ProjectType::Target::Type type) const override
  40. {
  41. switch (type)
  42. {
  43. case ProjectType::Target::GUIApp:
  44. case ProjectType::Target::StaticLibrary:
  45. case ProjectType::Target::StandalonePlugIn:
  46. return true;
  47. default:
  48. break;
  49. }
  50. return false;
  51. }
  52. //==============================================================================
  53. void addPlatformSpecificSettingsForProjectType (const ProjectType&) override
  54. {
  55. // no-op.
  56. }
  57. //==============================================================================
  58. void createExporterProperties (PropertyListBuilder& props) override
  59. {
  60. createBaseExporterProperties (props);
  61. createToolchainExporterProperties (props);
  62. createManifestExporterProperties (props);
  63. createLibraryModuleExporterProperties (props);
  64. createCodeSigningExporterProperties (props);
  65. createOtherExporterProperties (props);
  66. }
  67. static const char* getName() { return "Android"; }
  68. static const char* getValueTreeTypeName() { return "ANDROIDSTUDIO"; }
  69. static AndroidProjectExporter* createForSettings (Project& project, const ValueTree& settings)
  70. {
  71. if (settings.hasType (getValueTreeTypeName()))
  72. return new AndroidProjectExporter (project, settings);
  73. return nullptr;
  74. }
  75. //==============================================================================
  76. void initialiseDependencyPathValues() override
  77. {
  78. sdkPath.referTo (Value (new DependencyPathValueSource (getSetting (Ids::androidSDKPath), Ids::androidSDKPath, TargetOS::getThisOS())));
  79. ndkPath.referTo (Value (new DependencyPathValueSource (getSetting (Ids::androidNDKPath), Ids::androidNDKPath, TargetOS::getThisOS())));
  80. }
  81. //==============================================================================
  82. ValueWithDefault androidJavaLibs, androidRepositories, androidDependencies, androidScreenOrientation, androidActivityClass,
  83. androidActivitySubClassName, androidActivityBaseClassName, androidManifestCustomXmlElements, androidVersionCode,
  84. androidMinimumSDK, androidTheme, androidSharedLibraries, androidStaticLibraries, androidExtraAssetsFolder,
  85. androidOboeRepositoryPath, androidInternetNeeded, androidMicNeeded, androidCameraNeeded, androidBluetoothNeeded, androidExternalReadPermission,
  86. androidExternalWritePermission, androidInAppBillingPermission, androidVibratePermission,androidOtherPermissions,
  87. androidEnableRemoteNotifications, androidRemoteNotificationsConfigFile, androidEnableContentSharing, androidKeyStore,
  88. androidKeyStorePass, androidKeyAlias, androidKeyAliasPass, gradleVersion, gradleToolchain, androidPluginVersion, buildToolsVersion;
  89. //==============================================================================
  90. AndroidProjectExporter (Project& p, const ValueTree& t)
  91. : ProjectExporter (p, t),
  92. androidJavaLibs (settings, Ids::androidJavaLibs, getUndoManager()),
  93. androidRepositories (settings, Ids::androidRepositories, getUndoManager()),
  94. androidDependencies (settings, Ids::androidDependencies, getUndoManager()),
  95. androidScreenOrientation (settings, Ids::androidScreenOrientation, getUndoManager(), "unspecified"),
  96. androidActivityClass (settings, Ids::androidActivityClass, getUndoManager(), createDefaultClassName()),
  97. androidActivitySubClassName (settings, Ids::androidActivitySubClassName, getUndoManager()),
  98. androidActivityBaseClassName (settings, Ids::androidActivityBaseClassName, getUndoManager(), "Activity"),
  99. androidManifestCustomXmlElements (settings, Ids::androidManifestCustomXmlElements, getUndoManager()),
  100. androidVersionCode (settings, Ids::androidVersionCode, getUndoManager(), "1"),
  101. androidMinimumSDK (settings, Ids::androidMinimumSDK, getUndoManager(), "10"),
  102. androidTheme (settings, Ids::androidTheme, getUndoManager()),
  103. androidSharedLibraries (settings, Ids::androidSharedLibraries, getUndoManager()),
  104. androidStaticLibraries (settings, Ids::androidStaticLibraries, getUndoManager()),
  105. androidExtraAssetsFolder (settings, Ids::androidExtraAssetsFolder, getUndoManager()),
  106. androidOboeRepositoryPath (settings, Ids::androidOboeRepositoryPath, getUndoManager()),
  107. androidInternetNeeded (settings, Ids::androidInternetNeeded, getUndoManager(), true),
  108. androidMicNeeded (settings, Ids::microphonePermissionNeeded, getUndoManager(), false),
  109. androidCameraNeeded (settings, Ids::cameraPermissionNeeded, getUndoManager(), false),
  110. androidBluetoothNeeded (settings, Ids::androidBluetoothNeeded, getUndoManager(), true),
  111. androidExternalReadPermission (settings, Ids::androidExternalReadNeeded, getUndoManager(), true),
  112. androidExternalWritePermission (settings, Ids::androidExternalWriteNeeded, getUndoManager(), true),
  113. androidInAppBillingPermission (settings, Ids::androidInAppBilling, getUndoManager(), false),
  114. androidVibratePermission (settings, Ids::androidVibratePermissionNeeded, getUndoManager(), false),
  115. androidOtherPermissions (settings, Ids::androidOtherPermissions, getUndoManager()),
  116. androidEnableRemoteNotifications (settings, Ids::androidEnableRemoteNotifications, getUndoManager(), false),
  117. androidRemoteNotificationsConfigFile (settings, Ids::androidRemoteNotificationsConfigFile, getUndoManager()),
  118. androidEnableContentSharing (settings, Ids::androidEnableContentSharing, getUndoManager(), false),
  119. androidKeyStore (settings, Ids::androidKeyStore, getUndoManager(), "${user.home}/.android/debug.keystore"),
  120. androidKeyStorePass (settings, Ids::androidKeyStorePass, getUndoManager(), "android"),
  121. androidKeyAlias (settings, Ids::androidKeyAlias, getUndoManager(), "androiddebugkey"),
  122. androidKeyAliasPass (settings, Ids::androidKeyAliasPass, getUndoManager(), "android"),
  123. gradleVersion (settings, Ids::gradleVersion, getUndoManager(), "4.4"),
  124. gradleToolchain (settings, Ids::gradleToolchain, getUndoManager(), "clang"),
  125. androidPluginVersion (settings, Ids::androidPluginVersion, getUndoManager(), "3.1.3"),
  126. buildToolsVersion (settings, Ids::buildToolsVersion, getUndoManager(), "28.0.0"),
  127. AndroidExecutable (getAppSettings().getStoredPath (Ids::androidStudioExePath).toString())
  128. {
  129. name = getName();
  130. targetLocationValue.setDefault (getDefaultBuildsRootFolder() + getTargetFolderForExporter (getValueTreeTypeName()));
  131. }
  132. //==============================================================================
  133. void createToolchainExporterProperties (PropertyListBuilder& props)
  134. {
  135. props.add (new TextPropertyComponent (gradleVersion, "gradle version", 32, false),
  136. "The version of gradle that is used to build this app (4.4 is fine for JUCE)");
  137. props.add (new TextPropertyComponent (androidPluginVersion, "android plug-in version", 32, false),
  138. "The version of the android build plugin for gradle that is used to build this app");
  139. props.add (new ChoicePropertyComponent (gradleToolchain, "NDK Toolchain",
  140. { "clang", "gcc" },
  141. { "clang", "gcc" }),
  142. "The toolchain that gradle should invoke for NDK compilation (variable model.android.ndk.tooclhain in app/build.gradle)");
  143. props.add (new TextPropertyComponent (buildToolsVersion, "Android build tools version", 32, false),
  144. "The Android build tools version that should use to build this app");
  145. }
  146. void createLibraryModuleExporterProperties (PropertyListBuilder& props)
  147. {
  148. props.add (new TextPropertyComponent (androidStaticLibraries, "Import static library modules", 8192, true),
  149. "Comma or whitespace delimited list of static libraries (.a) defined in NDK_MODULE_PATH.");
  150. props.add (new TextPropertyComponent (androidSharedLibraries, "Import shared library modules", 8192, true),
  151. "Comma or whitespace delimited list of shared libraries (.so) defined in NDK_MODULE_PATH.");
  152. }
  153. //==============================================================================
  154. bool canLaunchProject() override
  155. {
  156. return AndroidExecutable.exists();
  157. }
  158. bool launchProject() override
  159. {
  160. if (! AndroidExecutable.exists())
  161. {
  162. jassertfalse;
  163. return false;
  164. }
  165. auto targetFolder = getTargetFolder();
  166. // we have to surround the path with extra quotes, otherwise Android Studio
  167. // will choke if there are any space characters in the path.
  168. return AndroidExecutable.startAsProcess ("\"" + targetFolder.getFullPathName() + "\"");
  169. }
  170. //==============================================================================
  171. void create (const OwnedArray<LibraryModule>& modules) const override
  172. {
  173. auto targetFolder = getTargetFolder();
  174. auto appFolder = targetFolder.getChildFile (isLibrary() ? "lib" : "app");
  175. removeOldFiles (targetFolder);
  176. if (! isLibrary())
  177. copyJavaFiles (modules);
  178. copyExtraResourceFiles();
  179. writeFile (targetFolder, "settings.gradle", isLibrary() ? "include ':lib'" : "include ':app'");
  180. writeFile (targetFolder, "build.gradle", getProjectBuildGradleFileContent());
  181. writeFile (appFolder, "build.gradle", getAppBuildGradleFileContent());
  182. writeFile (targetFolder, "local.properties", getLocalPropertiesFileContent());
  183. writeFile (targetFolder, "gradle/wrapper/gradle-wrapper.properties", getGradleWrapperPropertiesFileContent());
  184. writeBinaryFile (targetFolder, "gradle/wrapper/LICENSE-for-gradlewrapper.txt", BinaryData::LICENSE, BinaryData::LICENSESize);
  185. writeBinaryFile (targetFolder, "gradle/wrapper/gradle-wrapper.jar", BinaryData::gradlewrapper_jar, BinaryData::gradlewrapper_jarSize);
  186. writeBinaryFile (targetFolder, "gradlew", BinaryData::gradlew, BinaryData::gradlewSize);
  187. writeBinaryFile (targetFolder, "gradlew.bat", BinaryData::gradlew_bat, BinaryData::gradlew_batSize);
  188. targetFolder.getChildFile ("gradlew").setExecutePermission (true);
  189. writeAndroidManifest (appFolder);
  190. if (! isLibrary())
  191. {
  192. writeStringsXML (targetFolder);
  193. writeAppIcons (targetFolder);
  194. }
  195. writeCmakeFile (appFolder.getChildFile ("CMakeLists.txt"));
  196. auto androidExtraAssetsFolderValue = androidExtraAssetsFolder.get().toString();
  197. if (androidExtraAssetsFolderValue.isNotEmpty())
  198. {
  199. auto extraAssets = getProject().getFile().getParentDirectory().getChildFile (androidExtraAssetsFolderValue);
  200. if (extraAssets.exists() && extraAssets.isDirectory())
  201. {
  202. auto assetsFolder = appFolder.getChildFile ("src/main/assets");
  203. if (assetsFolder.deleteRecursively())
  204. extraAssets.copyDirectoryTo (assetsFolder);
  205. }
  206. }
  207. }
  208. void removeOldFiles (const File& targetFolder) const
  209. {
  210. targetFolder.getChildFile ("app/build").deleteRecursively();
  211. targetFolder.getChildFile ("app/build.gradle").deleteFile();
  212. targetFolder.getChildFile ("gradle").deleteRecursively();
  213. targetFolder.getChildFile ("local.properties").deleteFile();
  214. targetFolder.getChildFile ("settings.gradle").deleteFile();
  215. }
  216. void writeFile (const File& gradleProjectFolder, const String& filePath, const String& fileContent) const
  217. {
  218. MemoryOutputStream outStream;
  219. outStream << fileContent;
  220. overwriteFileIfDifferentOrThrow (gradleProjectFolder.getChildFile (filePath), outStream);
  221. }
  222. void writeBinaryFile (const File& gradleProjectFolder, const String& filePath, const char* binaryData, const int binarySize) const
  223. {
  224. MemoryOutputStream outStream;
  225. outStream.write (binaryData, static_cast<size_t> (binarySize));
  226. overwriteFileIfDifferentOrThrow (gradleProjectFolder.getChildFile (filePath), outStream);
  227. }
  228. protected:
  229. //==============================================================================
  230. class AndroidBuildConfiguration : public BuildConfiguration
  231. {
  232. public:
  233. AndroidBuildConfiguration (Project& p, const ValueTree& settings, const ProjectExporter& e)
  234. : BuildConfiguration (p, settings, e),
  235. androidArchitectures (config, Ids::androidArchitectures, getUndoManager(), isDebug() ? "armeabi-v7a x86" : ""),
  236. androidBuildConfigRemoteNotifsConfigFile (config, Ids::androidBuildConfigRemoteNotifsConfigFile, getUndoManager()),
  237. androidAdditionalXmlValueResources (config, Ids::androidAdditionalXmlValueResources, getUndoManager()),
  238. androidAdditionalDrawableResources (config, Ids::androidAdditionalDrawableResources, getUndoManager()),
  239. androidAdditionalRawValueResources (config, Ids::androidAdditionalRawValueResources, getUndoManager()),
  240. androidCustomStringXmlElements (config, Ids::androidCustomStringXmlElements, getUndoManager())
  241. {
  242. linkTimeOptimisationValue.setDefault (false);
  243. optimisationLevelValue.setDefault (isDebug() ? gccO0 : gccO3);
  244. }
  245. String getArchitectures() const { return androidArchitectures.get().toString(); }
  246. String getRemoteNotifsConfigFile() const { return androidBuildConfigRemoteNotifsConfigFile.get().toString(); }
  247. String getAdditionalXmlResources() const { return androidAdditionalXmlValueResources.get().toString(); }
  248. String getAdditionalDrawableResources() const { return androidAdditionalDrawableResources.get().toString(); }
  249. String getAdditionalRawResources() const { return androidAdditionalRawValueResources.get().toString();}
  250. String getCustomStringsXml() const { return androidCustomStringXmlElements.get().toString(); }
  251. void createConfigProperties (PropertyListBuilder& props) override
  252. {
  253. addGCCOptimisationProperty (props);
  254. props.add (new TextPropertyComponent (androidArchitectures, "Architectures", 256, false),
  255. "A list of the ARM architectures to build (for a fat binary). Leave empty to build for all possible android archiftectures.");
  256. props.add (new TextPropertyComponent (androidBuildConfigRemoteNotifsConfigFile.getPropertyAsValue(), "Remote Notifications Config File", 2048, false),
  257. "Path to google-services.json file. This will be the file provided by Firebase when creating a new app in Firebase console. "
  258. "This will override the setting from the main Android exporter node.");
  259. props.add (new TextPropertyComponent (androidAdditionalXmlValueResources, "Extra Android XML Value Resources", 8192, true),
  260. "Paths to additional \"value resource\" files in XML format that should be included in the app (one per line). "
  261. "If you have additional XML resources that should be treated as value resources, add them here.");
  262. props.add (new TextPropertyComponent (androidAdditionalDrawableResources, "Extra Android Drawable Resources", 8192, true),
  263. "Paths to additional \"drawable resource\" directories that should be included in the app (one per line). "
  264. "They will be added to \"res\" directory of Android project. "
  265. "Each path should point to a directory named \"drawable\" or \"drawable-<size>\" where <size> should be "
  266. "something like \"hdpi\", \"ldpi\", \"xxxhdpi\" etc, for instance \"drawable-xhdpi\". "
  267. "Refer to Android Studio documentation for available sizes.");
  268. props.add (new TextPropertyComponent (androidAdditionalRawValueResources, "Extra Android Raw Resources", 8192, true),
  269. "Paths to additional \"raw resource\" files that should be included in the app (one per line). "
  270. "Resource file names must contain only lowercase a-z, 0-9 or underscore.");
  271. props.add (new TextPropertyComponent (androidCustomStringXmlElements, "Custom string resources", 8192, true),
  272. "Custom XML resources that will be added to string.xml as children of <resources> element. "
  273. "Example: \n<string name=\"value\">text</string>\n"
  274. "<string name2=\"value2\">text2</string>\n");
  275. }
  276. String getProductFlavourNameIdentifier() const
  277. {
  278. return getName().toLowerCase().replaceCharacter (L' ', L'_') + String ("_");
  279. }
  280. String getProductFlavourCMakeIdentifier() const
  281. {
  282. return getName().toUpperCase().replaceCharacter (L' ', L'_');
  283. }
  284. String getModuleLibraryArchName() const override
  285. {
  286. return "${ANDROID_ABI}";
  287. }
  288. ValueWithDefault androidArchitectures, androidBuildConfigRemoteNotifsConfigFile,
  289. androidAdditionalXmlValueResources, androidAdditionalDrawableResources,
  290. androidAdditionalRawValueResources, androidCustomStringXmlElements;
  291. };
  292. BuildConfiguration::Ptr createBuildConfig (const ValueTree& v) const override
  293. {
  294. return new AndroidBuildConfiguration (project, v, *this);
  295. }
  296. private:
  297. void writeCmakeFile (const File& file) const
  298. {
  299. MemoryOutputStream mo;
  300. mo << "# Automatically generated makefile, created by the Projucer" << newLine
  301. << "# Don't edit this file! Your changes will be overwritten when you re-save the Projucer project!" << newLine
  302. << newLine;
  303. mo << "cmake_minimum_required(VERSION 3.4.1)" << newLine << newLine;
  304. if (! isLibrary())
  305. mo << "SET(BINARY_NAME \"juce_jni\")" << newLine << newLine;
  306. if (project.getConfigFlag ("JUCE_USE_ANDROID_OBOE").get())
  307. {
  308. String oboePath (androidOboeRepositoryPath.get().toString().quoted());
  309. mo << "SET(OBOE_DIR " << oboePath << ")" << newLine << newLine;
  310. mo << "add_subdirectory (${OBOE_DIR} ./oboe)" << newLine << newLine;
  311. }
  312. String cpufeaturesPath ("${ANDROID_NDK}/sources/android/cpufeatures/cpu-features.c");
  313. mo << "add_library(\"cpufeatures\" STATIC \"" << cpufeaturesPath << "\")" << newLine
  314. << "set_source_files_properties(\"" << cpufeaturesPath << "\" PROPERTIES COMPILE_FLAGS \"-Wno-sign-conversion -Wno-gnu-statement-expression\")" << newLine << newLine;
  315. {
  316. auto projectDefines = getEscapedPreprocessorDefs (getProjectPreprocessorDefs());
  317. if (projectDefines.size() > 0)
  318. mo << "add_definitions(" << projectDefines.joinIntoString (" ") << ")" << newLine << newLine;
  319. }
  320. {
  321. mo << "include_directories( AFTER" << newLine;
  322. for (auto& path : extraSearchPaths)
  323. mo << " \"" << escapeDirectoryForCmake (path) << "\"" << newLine;
  324. mo << " \"${ANDROID_NDK}/sources/android/cpufeatures\"" << newLine;
  325. if (project.getConfigFlag ("JUCE_USE_ANDROID_OBOE").get())
  326. mo << " \"${OBOE_DIR}/include\"" << newLine;
  327. mo << ")" << newLine << newLine;
  328. }
  329. auto cfgExtraLinkerFlags = getExtraLinkerFlagsString();
  330. if (cfgExtraLinkerFlags.isNotEmpty())
  331. {
  332. mo << "SET( JUCE_LDFLAGS \"" << cfgExtraLinkerFlags.replace ("\"", "\\\"") << "\")" << newLine;
  333. mo << "SET( CMAKE_SHARED_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} ${JUCE_LDFLAGS}\")" << newLine << newLine;
  334. }
  335. mo << "enable_language(ASM)" << newLine << newLine;
  336. auto userLibraries = StringArray::fromTokens (getExternalLibrariesString(), ";", "");
  337. userLibraries.addArray (androidLibs);
  338. if (getNumConfigurations() > 0)
  339. {
  340. bool first = true;
  341. for (ConstConfigIterator config (*this); config.next();)
  342. {
  343. auto& cfg = dynamic_cast<const AndroidBuildConfiguration&> (*config);
  344. auto libSearchPaths = cfg.getLibrarySearchPaths();
  345. auto cfgDefines = getConfigPreprocessorDefs (cfg);
  346. auto cfgHeaderPaths = cfg.getHeaderSearchPaths();
  347. auto cfgLibraryPaths = cfg.getLibrarySearchPaths();
  348. if (! isLibrary() && libSearchPaths.size() == 0 && cfgDefines.size() == 0
  349. && cfgHeaderPaths.size() == 0 && cfgLibraryPaths.size() == 0)
  350. continue;
  351. mo << (first ? "IF" : "ELSEIF") << "(JUCE_BUILD_CONFIGURATION MATCHES \"" << cfg.getProductFlavourCMakeIdentifier() <<"\")" << newLine;
  352. if (isLibrary())
  353. {
  354. mo << " SET(BINARY_NAME \"" << getNativeModuleBinaryName (cfg) << "\")" << newLine;
  355. auto binaryLocation = cfg.getTargetBinaryRelativePathString();
  356. if (binaryLocation.isNotEmpty())
  357. {
  358. auto locationRelativeToCmake = RelativePath (binaryLocation, RelativePath::projectFolder)
  359. .rebased (getProject().getFile().getParentDirectory(),
  360. file.getParentDirectory(), RelativePath::buildTargetFolder);
  361. mo << " SET(CMAKE_ARCHIVE_OUTPUT_DIRECTORY \"" << "../../../../" << locationRelativeToCmake.toUnixStyle() << "\")" << newLine;
  362. }
  363. }
  364. writeCmakePathLines (mo, " ", "link_directories(", libSearchPaths);
  365. if (cfgDefines.size() > 0)
  366. mo << " add_definitions(" << getEscapedPreprocessorDefs (cfgDefines).joinIntoString (" ") << ")" << newLine;
  367. writeCmakePathLines (mo, " ", "include_directories( AFTER", cfgHeaderPaths);
  368. if (userLibraries.size() > 0)
  369. {
  370. for (auto& lib : userLibraries)
  371. {
  372. String findLibraryCmd;
  373. findLibraryCmd << "find_library(" << lib.toLowerCase().replaceCharacter (L' ', L'_')
  374. << " \"" << lib << "\" PATHS";
  375. writeCmakePathLines (mo, " ", findLibraryCmd, cfgLibraryPaths, " NO_CMAKE_FIND_ROOT_PATH)");
  376. }
  377. mo << newLine;
  378. }
  379. if (cfg.isLinkTimeOptimisationEnabled())
  380. {
  381. // There's no MIPS support for LTO
  382. String mipsCondition ("NOT (ANDROID_ABI STREQUAL \"mips\" OR ANDROID_ABI STREQUAL \"mips64\")");
  383. mo << " if(" << mipsCondition << ")" << newLine;
  384. StringArray cmakeVariables ("CMAKE_C_FLAGS", "CMAKE_CXX_FLAGS", "CMAKE_EXE_LINKER_FLAGS");
  385. for (auto& variable : cmakeVariables)
  386. {
  387. auto configVariable = variable + "_" + cfg.getProductFlavourCMakeIdentifier();
  388. mo << " SET(" << configVariable << " \"${" << configVariable << "} -flto\")" << newLine;
  389. }
  390. mo << " ENDIF(" << mipsCondition << ")" << newLine;
  391. }
  392. first = false;
  393. }
  394. if (! first)
  395. {
  396. ProjectExporter::BuildConfiguration::Ptr config (getConfiguration(0));
  397. if (config)
  398. {
  399. if (auto* cfg = dynamic_cast<const AndroidBuildConfiguration*> (config.get()))
  400. {
  401. mo << "ELSE(JUCE_BUILD_CONFIGURATION MATCHES \"" << cfg->getProductFlavourCMakeIdentifier() <<"\")" << newLine;
  402. mo << " MESSAGE( FATAL_ERROR \"No matching build-configuration found.\" )" << newLine;
  403. mo << "ENDIF(JUCE_BUILD_CONFIGURATION MATCHES \"" << cfg->getProductFlavourCMakeIdentifier() <<"\")" << newLine << newLine;
  404. }
  405. }
  406. }
  407. }
  408. Array<RelativePath> excludeFromBuild;
  409. mo << "add_library( ${BINARY_NAME}" << newLine;
  410. mo << newLine;
  411. mo << " " << (getProject().getProjectType().isStaticLibrary() ? "STATIC" : "SHARED") << newLine;
  412. mo << newLine;
  413. addCompileUnits (mo, excludeFromBuild);
  414. mo << ")" << newLine << newLine;
  415. if (excludeFromBuild.size() > 0)
  416. {
  417. for (auto& exclude : excludeFromBuild)
  418. mo << "set_source_files_properties(\"" << exclude.toUnixStyle() << "\" PROPERTIES HEADER_FILE_ONLY TRUE)" << newLine;
  419. mo << newLine;
  420. }
  421. auto libraries = getAndroidLibraries();
  422. if (libraries.size() > 0)
  423. {
  424. for (auto& lib : libraries)
  425. mo << "find_library(" << lib.toLowerCase().replaceCharacter (L' ', L'_') << " \"" << lib << "\")" << newLine;
  426. mo << newLine;
  427. }
  428. libraries.addArray (userLibraries);
  429. mo << "target_link_libraries( ${BINARY_NAME}";
  430. if (libraries.size() > 0)
  431. {
  432. mo << newLine << newLine;
  433. for (auto& lib : libraries)
  434. mo << " ${" << lib.toLowerCase().replaceCharacter (L' ', L'_') << "}" << newLine;
  435. mo << " \"cpufeatures\"" << newLine;
  436. }
  437. if (project.getConfigFlag ("JUCE_USE_ANDROID_OBOE").get())
  438. mo << " \"oboe\"" << newLine;
  439. mo << ")" << newLine;
  440. overwriteFileIfDifferentOrThrow (file, mo);
  441. }
  442. //==============================================================================
  443. String getProjectBuildGradleFileContent() const
  444. {
  445. MemoryOutputStream mo;
  446. mo << "buildscript {" << newLine;
  447. mo << " repositories {" << newLine;
  448. mo << " google()" << newLine;
  449. mo << " jcenter()" << newLine;
  450. mo << " }" << newLine;
  451. mo << " dependencies {" << newLine;
  452. mo << " classpath 'com.android.tools.build:gradle:" << androidPluginVersion.get().toString() << "'" << newLine;
  453. if (androidEnableRemoteNotifications.get())
  454. mo << " classpath 'com.google.gms:google-services:3.1.0'" << newLine;
  455. mo << " }" << newLine;
  456. mo << "}" << newLine;
  457. mo << "" << newLine;
  458. mo << "allprojects {" << newLine;
  459. mo << " repositories {" << newLine;
  460. mo << " google()" << newLine;
  461. mo << " jcenter()" << newLine;
  462. if (androidEnableRemoteNotifications.get())
  463. {
  464. mo << " maven {" << newLine;
  465. mo << " url \"https://maven.google.com\"" << newLine;
  466. mo << " }" << newLine;
  467. }
  468. mo << " }" << newLine;
  469. mo << "}" << newLine;
  470. return mo.toString();
  471. }
  472. //==============================================================================
  473. String getAppBuildGradleFileContent() const
  474. {
  475. MemoryOutputStream mo;
  476. mo << "apply plugin: 'com.android." << (isLibrary() ? "library" : "application") << "'" << newLine << newLine;
  477. mo << "android {" << newLine;
  478. mo << " compileSdkVersion " << static_cast<int> (androidMinimumSDK.get()) << newLine;
  479. mo << " buildToolsVersion \"" << buildToolsVersion.get().toString() << "\"" << newLine;
  480. mo << " externalNativeBuild {" << newLine;
  481. mo << " cmake {" << newLine;
  482. mo << " path \"CMakeLists.txt\"" << newLine;
  483. mo << " }" << newLine;
  484. mo << " }" << newLine;
  485. mo << getAndroidSigningConfig() << newLine;
  486. mo << getAndroidDefaultConfig() << newLine;
  487. mo << getAndroidBuildTypes() << newLine;
  488. mo << getAndroidProductFlavours() << newLine;
  489. mo << getAndroidVariantFilter() << newLine;
  490. mo << getAndroidRepositories() << newLine;
  491. mo << getAndroidDependencies() << newLine;
  492. mo << getApplyPlugins() << newLine;
  493. mo << "}" << newLine << newLine;
  494. return mo.toString();
  495. }
  496. String getAndroidProductFlavours() const
  497. {
  498. MemoryOutputStream mo;
  499. mo << " flavorDimensions \"default\"" << newLine;
  500. mo << " productFlavors {" << newLine;
  501. for (ConstConfigIterator config (*this); config.next();)
  502. {
  503. auto& cfg = dynamic_cast<const AndroidBuildConfiguration&> (*config);
  504. mo << " " << cfg.getProductFlavourNameIdentifier() << " {" << newLine;
  505. if (cfg.getArchitectures().isNotEmpty())
  506. {
  507. mo << " ndk {" << newLine;
  508. mo << " abiFilters " << toGradleList (StringArray::fromTokens (cfg.getArchitectures(), " ", "")) << newLine;
  509. mo << " }" << newLine;
  510. }
  511. mo << " externalNativeBuild {" << newLine;
  512. mo << " cmake {" << newLine;
  513. if (getProject().getProjectType().isStaticLibrary())
  514. mo << " targets \"" << getNativeModuleBinaryName (cfg) << "\"" << newLine;
  515. mo << " arguments \"-DJUCE_BUILD_CONFIGURATION=" << cfg.getProductFlavourCMakeIdentifier() << "\""
  516. << ", \"-DCMAKE_CXX_FLAGS_" << (cfg.isDebug() ? "DEBUG" : "RELEASE")
  517. << "=-O" << cfg.getGCCOptimisationFlag() << "\""
  518. << ", \"-DCMAKE_C_FLAGS_" << (cfg.isDebug() ? "DEBUG" : "RELEASE")
  519. << "=-O" << cfg.getGCCOptimisationFlag() << "\"" << newLine;
  520. mo << " }" << newLine;
  521. mo << " }" << newLine << newLine;
  522. mo << " dimension \"default\"" << newLine;
  523. mo << " }" << newLine;
  524. }
  525. mo << " }" << newLine;
  526. return mo.toString();
  527. }
  528. String getAndroidSigningConfig() const
  529. {
  530. MemoryOutputStream mo;
  531. auto keyStoreFilePath = androidKeyStore.get().toString().replace ("${user.home}", "${System.properties['user.home']}")
  532. .replace ("/", "${File.separator}");
  533. mo << " signingConfigs {" << newLine;
  534. mo << " juceSigning {" << newLine;
  535. mo << " storeFile file(\"" << keyStoreFilePath << "\")" << newLine;
  536. mo << " storePassword \"" << androidKeyStorePass.get().toString() << "\"" << newLine;
  537. mo << " keyAlias \"" << androidKeyAlias.get().toString() << "\"" << newLine;
  538. mo << " keyPassword \"" << androidKeyAliasPass.get().toString() << "\"" << newLine;
  539. mo << " storeType \"jks\"" << newLine;
  540. mo << " }" << newLine;
  541. mo << " }" << newLine;
  542. return mo.toString();
  543. }
  544. String getAndroidDefaultConfig() const
  545. {
  546. auto bundleIdentifier = project.getBundleIdentifierString().toLowerCase();
  547. auto cmakeDefs = getCmakeDefinitions();
  548. auto cFlags = getProjectCompilerFlags();
  549. auto cxxFlags = getProjectCxxCompilerFlags();
  550. auto minSdkVersion = static_cast<int> (androidMinimumSDK.get());
  551. MemoryOutputStream mo;
  552. mo << " defaultConfig {" << newLine;
  553. if (! isLibrary())
  554. mo << " applicationId \"" << bundleIdentifier << "\"" << newLine;
  555. mo << " minSdkVersion " << minSdkVersion << newLine;
  556. mo << " targetSdkVersion " << minSdkVersion << newLine;
  557. mo << " externalNativeBuild {" << newLine;
  558. mo << " cmake {" << newLine;
  559. mo << " arguments " << cmakeDefs.joinIntoString (", ") << newLine;
  560. if (cFlags.size() > 0)
  561. mo << " cFlags " << cFlags.joinIntoString (", ") << newLine;
  562. if (cxxFlags.size() > 0)
  563. mo << " cppFlags " << cxxFlags.joinIntoString (", ") << newLine;
  564. mo << " }" << newLine;
  565. mo << " }" << newLine;
  566. mo << " }" << newLine;
  567. return mo.toString();
  568. }
  569. String getAndroidBuildTypes() const
  570. {
  571. MemoryOutputStream mo;
  572. mo << " buildTypes {" << newLine;
  573. int numDebugConfigs = 0;
  574. auto numConfigs = getNumConfigurations();
  575. for (int i = 0; i < numConfigs; ++i)
  576. {
  577. auto config = getConfiguration(i);
  578. if (config->isDebug()) numDebugConfigs++;
  579. if (numDebugConfigs > 1 || ((numConfigs - numDebugConfigs) > 1))
  580. continue;
  581. mo << " " << (config->isDebug() ? "debug" : "release") << " {" << newLine;
  582. mo << " initWith " << (config->isDebug() ? "debug" : "release") << newLine;
  583. mo << " debuggable " << (config->isDebug() ? "true" : "false") << newLine;
  584. mo << " jniDebuggable " << (config->isDebug() ? "true" : "false") << newLine;
  585. mo << " signingConfig signingConfigs.juceSigning" << newLine;
  586. mo << " }" << newLine;
  587. }
  588. mo << " }" << newLine;
  589. return mo.toString();
  590. }
  591. String getAndroidVariantFilter() const
  592. {
  593. MemoryOutputStream mo;
  594. mo << " variantFilter { variant ->" << newLine;
  595. mo << " def names = variant.flavors*.name" << newLine;
  596. for (ConstConfigIterator config (*this); config.next();)
  597. {
  598. auto& cfg = dynamic_cast<const AndroidBuildConfiguration&> (*config);
  599. mo << " if (names.contains (\"" << cfg.getProductFlavourNameIdentifier() << "\")" << newLine;
  600. mo << " && variant.buildType.name != \"" << (cfg.isDebug() ? "debug" : "release") << "\") {" << newLine;
  601. mo << " setIgnore(true)" << newLine;
  602. mo << " }" << newLine;
  603. }
  604. mo << " }" << newLine;
  605. return mo.toString();
  606. }
  607. String getAndroidRepositories() const
  608. {
  609. MemoryOutputStream mo;
  610. auto repositories = StringArray::fromLines (androidRepositories.get().toString());
  611. mo << "repositories {" << newLine;
  612. for (auto& r : repositories)
  613. mo << " " << r << newLine;
  614. mo << "}" << newLine;
  615. return mo.toString();
  616. }
  617. String getAndroidDependencies() const
  618. {
  619. MemoryOutputStream mo;
  620. mo << "dependencies {" << newLine;
  621. for (auto& d : StringArray::fromLines (androidDependencies.get().toString()))
  622. mo << " " << d << newLine;
  623. for (auto& d : StringArray::fromLines (androidJavaLibs.get().toString()))
  624. mo << " implementation files('libs/" << File (d).getFileName() << "')" << newLine;
  625. if (androidEnableRemoteNotifications.get())
  626. {
  627. mo << " 'com.google.firebase:firebase-core:11.4.0'" << newLine;
  628. mo << " compile 'com.google.firebase:firebase-messaging:11.4.0'" << newLine;
  629. }
  630. mo << "}" << newLine;
  631. return mo.toString();
  632. }
  633. String getApplyPlugins() const
  634. {
  635. MemoryOutputStream mo;
  636. if (androidEnableRemoteNotifications.get())
  637. mo << "apply plugin: 'com.google.gms.google-services'" << newLine;
  638. return mo.toString();
  639. }
  640. //==============================================================================
  641. String getLocalPropertiesFileContent() const
  642. {
  643. String props;
  644. props << "ndk.dir=" << sanitisePath (ndkPath.toString()) << newLine
  645. << "sdk.dir=" << sanitisePath (sdkPath.toString()) << newLine;
  646. return props;
  647. }
  648. String getGradleWrapperPropertiesFileContent() const
  649. {
  650. String props;
  651. props << "distributionUrl=https\\://services.gradle.org/distributions/gradle-"
  652. << gradleVersion.get().toString() << "-all.zip";
  653. return props;
  654. }
  655. //==============================================================================
  656. void createBaseExporterProperties (PropertyListBuilder& props)
  657. {
  658. props.add (new TextPropertyComponent (androidJavaLibs, "Java libraries to include", 32768, true),
  659. "Java libs (JAR files) (one per line). These will be copied to app/libs folder and \"implementation files\" "
  660. "dependency will be automatically added to module \"dependencies\" section for each library, so do "
  661. "not add the dependency yourself.");
  662. props.add (new TextPropertyComponent (androidRepositories, "Module repositories", 32768, true),
  663. "Module repositories (one per line). These will be added to module-level gradle file repositories section. ");
  664. props.add (new TextPropertyComponent (androidDependencies, "Module dependencies", 32768, true),
  665. "Module dependencies (one per line). These will be added to module-level gradle file \"dependencies\" section. "
  666. "If adding any java libs in \"Java libraries to include\" setting, do not add them here as "
  667. "they will be added automatically.");
  668. props.add (new ChoicePropertyComponent (androidScreenOrientation, "Screen orientation",
  669. { "Portrait and Landscape", "Portrait", "Landscape" },
  670. { "unspecified", "portrait", "landscape" }),
  671. "The screen orientations that this app should support");
  672. props.add (new TextPropertyComponent (androidActivityClass, "Android Activity class name", 256, false),
  673. "The full java class name to use for the app's Activity class.");
  674. props.add (new TextPropertyComponent (androidActivitySubClassName, "Android Activity sub-class name", 256, false),
  675. "If not empty, specifies the Android Activity class name stored in the app's manifest. "
  676. "Use this if you would like to use your own Android Activity sub-class.");
  677. props.add (new TextPropertyComponent (androidActivityBaseClassName, "Android Activity base class", 256, false),
  678. "If not empty, specifies the base class to use for your activity. If custom base class is "
  679. "specified, that base class should be a sub-class of android.app.Activity. When empty, Activity "
  680. "(android.app.Activity) will be used as the base class. "
  681. "Use this if you would like to use your own Android Activity base class.");
  682. props.add (new TextPropertyComponent (androidVersionCode, "Android Version Code", 32, false),
  683. "An integer value that represents the version of the application code, relative to other versions.");
  684. props.add (new DependencyPathPropertyComponent (project.getFile().getParentDirectory(), sdkPath, "Android SDK Path"),
  685. "The path to the Android SDK folder on the target build machine");
  686. props.add (new DependencyPathPropertyComponent (project.getFile().getParentDirectory(), ndkPath, "Android NDK Path"),
  687. "The path to the Android NDK folder on the target build machine");
  688. props.add (new TextPropertyComponent (androidMinimumSDK, "Minimum SDK version", 32, false),
  689. "The number of the minimum version of the Android SDK that the app requires");
  690. props.add (new TextPropertyComponent (androidExtraAssetsFolder, "Extra Android Assets", 256, false),
  691. "A path to a folder (relative to the project folder) which contains extra android assets.");
  692. }
  693. //==============================================================================
  694. void createManifestExporterProperties (PropertyListBuilder& props)
  695. {
  696. props.add (new TextPropertyComponent (androidOboeRepositoryPath, "Oboe repository path", 2048, false),
  697. "Path to the root of Oboe repository. Make sure to point Oboe repository to "
  698. "commit with SHA 44c6b6ea9c8fa9b5b74cbd60f355068b57b50b37 before building.");
  699. props.add (new ChoicePropertyComponent (androidInternetNeeded, "Internet Access"),
  700. "If enabled, this will set the android.permission.INTERNET flag in the manifest.");
  701. props.add (new ChoicePropertyComponent (androidMicNeeded, "Audio Input Required"),
  702. "If enabled, this will set the android.permission.RECORD_AUDIO flag in the manifest.");
  703. props.add (new ChoicePropertyComponent (androidCameraNeeded, "Camera Required"),
  704. "If enabled, this will set the android.permission.CAMERA flag in the manifest.");
  705. props.add (new ChoicePropertyComponent (androidBluetoothNeeded, "Bluetooth permissions Required"),
  706. "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.");
  707. props.add (new ChoicePropertyComponent (androidExternalReadPermission, "Read from external storage"),
  708. "If enabled, this will set the android.permission.READ_EXTERNAL_STORAGE flag in the manifest.");
  709. props.add (new ChoicePropertyComponent (androidExternalWritePermission, "Write to external storage"),
  710. "If enabled, this will set the android.permission.WRITE_EXTERNAL_STORAGE flag in the manifest.");
  711. props.add (new ChoicePropertyComponent (androidInAppBillingPermission, "In-App Billing"),
  712. "If enabled, this will set the com.android.vending.BILLING flag in the manifest.");
  713. props.add (new ChoicePropertyComponent (androidVibratePermission, "Vibrate"),
  714. "If enabled, this will set the android.permission.VIBRATE flag in the manifest.");
  715. props.add (new ChoicePropertyComponent (androidEnableContentSharing, "Content Sharing"),
  716. "If enabled, your app will be able to share content with other apps.");
  717. props.add (new TextPropertyComponent (androidOtherPermissions, "Custom permissions", 2048, false),
  718. "A space-separated list of other permission flags that should be added to the manifest.");
  719. props.add (new ChoicePropertyComponent (androidEnableRemoteNotifications, "Remote Notifications"),
  720. "Enable to be able to send remote notifications to devices running your app (min API level 14). Provide Remote Notifications Config File, "
  721. "configure your app in Firebase Console and ensure you have the latest Google Repository in Android Studio's SDK Manager.");
  722. props.add (new TextPropertyComponent (androidRemoteNotificationsConfigFile.getPropertyAsValue(), "Remote Notifications Config File", 2048, false),
  723. "Path to google-services.json file. This will be the file provided by Firebase when creating a new app in Firebase console.");
  724. props.add (new TextPropertyComponent (androidManifestCustomXmlElements, "Custom manifest XML content", 8192, true),
  725. "You can specify custom AndroidManifest.xml content overriding the default one generated by Projucer. "
  726. "Projucer will automatically create any missing and required XML elements and attributes "
  727. "and merge them into your custom content.");
  728. }
  729. //==============================================================================
  730. void createCodeSigningExporterProperties (PropertyListBuilder& props)
  731. {
  732. props.add (new TextPropertyComponent (androidKeyStore, "Key Signing: key.store", 2048, false),
  733. "The key.store value, used when signing the release package.");
  734. props.add (new TextPropertyComponent (androidKeyStorePass, "Key Signing: key.store.password", 2048, false),
  735. "The key.store password, used when signing the release package.");
  736. props.add (new TextPropertyComponent (androidKeyAlias, "Key Signing: key.alias", 2048, false),
  737. "The key.alias value, used when signing the release package.");
  738. props.add (new TextPropertyComponent (androidKeyAliasPass, "Key Signing: key.alias.password", 2048, false),
  739. "The key.alias password, used when signing the release package.");
  740. }
  741. //==============================================================================
  742. void createOtherExporterProperties (PropertyListBuilder& props)
  743. {
  744. props.add (new TextPropertyComponent (androidTheme, "Android Theme", 256, false),
  745. "E.g. @android:style/Theme.NoTitleBar or leave blank for default");
  746. }
  747. //==============================================================================
  748. String createDefaultClassName() const
  749. {
  750. auto s = project.getBundleIdentifierString().toLowerCase();
  751. if (s.length() > 5
  752. && s.containsChar ('.')
  753. && s.containsOnly ("abcdefghijklmnopqrstuvwxyz_.")
  754. && ! s.startsWithChar ('.'))
  755. {
  756. if (! s.endsWithChar ('.'))
  757. s << ".";
  758. }
  759. else
  760. {
  761. s = "com.yourcompany.";
  762. }
  763. return s + CodeHelpers::makeValidIdentifier (project.getProjectFilenameRootString(), false, true, false);
  764. }
  765. //==============================================================================
  766. void copyJavaFiles (const OwnedArray<LibraryModule>& modules) const
  767. {
  768. if (auto* coreModule = getCoreModule (modules))
  769. {
  770. auto package = getActivityClassPackage();
  771. auto targetFolder = getTargetFolder();
  772. auto inAppBillingPath = String ("com.android.vending.billing").replaceCharacter ('.', File::getSeparatorChar());
  773. auto javaSourceFolder = coreModule->getFolder().getChildFile ("native").getChildFile ("java");
  774. auto javaInAppBillingTarget = targetFolder.getChildFile ("app/src/main/java").getChildFile (inAppBillingPath);
  775. auto javaTarget = targetFolder.getChildFile ("app/src/main/java")
  776. .getChildFile (package.replaceCharacter ('.', File::getSeparatorChar()));
  777. auto libTarget = targetFolder.getChildFile ("app/libs");
  778. libTarget.createDirectory();
  779. copyActivityJavaFiles (javaSourceFolder, javaTarget, package);
  780. copyServicesJavaFiles (javaSourceFolder, javaTarget, package);
  781. copyProviderJavaFile (javaSourceFolder, javaTarget, package);
  782. copyAdditionalJavaFiles (javaSourceFolder, javaInAppBillingTarget);
  783. copyAdditionalJavaLibs (libTarget);
  784. }
  785. }
  786. void copyActivityJavaFiles (const File& javaSourceFolder, const File& targetFolder, const String& package) const
  787. {
  788. if (androidActivityClass.get().toString().contains ("_"))
  789. throw SaveError ("Your Android activity class name or path may not contain any underscores! Try a project name without underscores.");
  790. auto className = getActivityName();
  791. if (className.isEmpty())
  792. throw SaveError ("Invalid Android Activity class name: " + androidActivityClass.get().toString());
  793. createDirectoryOrThrow (targetFolder);
  794. auto activityCode = getActivityCode (javaSourceFolder, className, package);
  795. auto javaDestFile = targetFolder.getChildFile (className + ".java");
  796. overwriteFileIfDifferentOrThrow (javaDestFile, activityCode);
  797. }
  798. String getActivityCode (const File& javaSourceFolder, const String& className, const String& package) const
  799. {
  800. auto runtimePermissionsCode = getRuntimePermissionsCode (javaSourceFolder, className);
  801. auto midiCode = getMidiCode (javaSourceFolder, className);
  802. auto webViewCode = getWebViewCode (javaSourceFolder);
  803. auto cameraCode = getCameraCode (javaSourceFolder);
  804. auto videoCode = getVideoCode (javaSourceFolder);
  805. auto javaSourceFile = javaSourceFolder.getChildFile ("JuceAppActivity.java");
  806. auto javaSourceLines = StringArray::fromLines (javaSourceFile.loadFileAsString());
  807. {
  808. MemoryOutputStream newFile;
  809. for (auto& line : javaSourceLines)
  810. {
  811. if (line.contains ("$$JuceAndroidMidiImports$$"))
  812. newFile << midiCode.imports;
  813. else if (line.contains ("$$JuceAndroidMidiCode$$"))
  814. newFile << midiCode.main;
  815. else if (line.contains ("$$JuceAndroidRuntimePermissionsCode$$"))
  816. newFile << runtimePermissionsCode;
  817. else if (line.contains ("$$JuceAndroidWebViewImports$$"))
  818. newFile << webViewCode.imports;
  819. else if (line.contains ("$$JuceAndroidWebViewNativeCode$$"))
  820. newFile << webViewCode.native;
  821. else if (line.contains ("$$JuceAndroidWebViewCode$$"))
  822. newFile << webViewCode.main;
  823. else if (line.contains ("$$JuceAndroidCameraImports$$"))
  824. newFile << cameraCode.imports;
  825. else if (line.contains ("$$JuceAndroidCameraCode$$"))
  826. newFile << cameraCode.main;
  827. else if (line.contains ("$$JuceAndroidVideoImports$$"))
  828. newFile << videoCode.imports;
  829. else if (line.contains ("$$JuceAndroidVideoCode$$"))
  830. newFile << videoCode.main;
  831. else
  832. newFile << line.replace ("$$JuceAppActivityBaseClass$$", androidActivityBaseClassName.get().toString())
  833. .replace ("JuceAppActivity", className)
  834. .replace ("package com.juce;", "package " + package + ";") << newLine;
  835. }
  836. javaSourceLines = StringArray::fromLines (newFile.toString());
  837. }
  838. while (javaSourceLines.size() > 2
  839. && javaSourceLines[javaSourceLines.size() - 1].trim().isEmpty()
  840. && javaSourceLines[javaSourceLines.size() - 2].trim().isEmpty())
  841. javaSourceLines.remove (javaSourceLines.size() - 1);
  842. return javaSourceLines.joinIntoString (newLine);
  843. }
  844. String getRuntimePermissionsCode (const File& javaSourceFolder, const String& className) const
  845. {
  846. if (static_cast<int> (androidMinimumSDK.get()) >= 23)
  847. {
  848. auto javaRuntimePermissions = javaSourceFolder.getChildFile ("AndroidRuntimePermissions.java");
  849. return javaRuntimePermissions.loadFileAsString().replace ("JuceAppActivity", className);
  850. }
  851. return {};
  852. }
  853. struct MidiCode
  854. {
  855. String imports;
  856. String main;
  857. };
  858. MidiCode getMidiCode (const File& javaSourceFolder, const String& className) const
  859. {
  860. String juceMidiCode, juceMidiImports;
  861. juceMidiImports << newLine;
  862. if (static_cast<int> (androidMinimumSDK.get()) >= 23)
  863. {
  864. auto javaAndroidMidi = javaSourceFolder.getChildFile ("AndroidMidi.java");
  865. juceMidiImports << "import android.media.midi.*;" << newLine
  866. << "import android.bluetooth.*;" << newLine
  867. << "import android.bluetooth.le.*;" << newLine;
  868. juceMidiCode = javaAndroidMidi.loadFileAsString().replace ("JuceAppActivity", className);
  869. }
  870. else
  871. {
  872. juceMidiCode = javaSourceFolder.getChildFile ("AndroidMidiFallback.java")
  873. .loadFileAsString()
  874. .replace ("JuceAppActivity", className);
  875. }
  876. return { juceMidiImports, juceMidiCode };
  877. }
  878. struct WebViewCode
  879. {
  880. String imports;
  881. String native;
  882. String main;
  883. };
  884. WebViewCode getWebViewCode (const File& javaSourceFolder) const
  885. {
  886. String juceWebViewImports, juceWebViewCodeNative, juceWebViewCode;
  887. if (static_cast<int> (androidMinimumSDK.get()) >= 23)
  888. juceWebViewImports << "import android.webkit.WebResourceError;" << newLine;
  889. if (static_cast<int> (androidMinimumSDK.get()) >= 21)
  890. juceWebViewImports << "import android.webkit.WebResourceRequest;" << newLine;
  891. if (static_cast<int> (androidMinimumSDK.get()) >= 11)
  892. juceWebViewImports << "import android.webkit.WebResourceResponse;" << newLine;
  893. auto javaWebViewFile = javaSourceFolder.getChildFile ("AndroidWebView.java");
  894. auto juceWebViewCodeAll = javaWebViewFile.loadFileAsString();
  895. if (static_cast<int> (androidMinimumSDK.get()) <= 10)
  896. {
  897. juceWebViewCode << juceWebViewCodeAll.fromFirstOccurrenceOf ("$$WebViewApi1_10", false, false)
  898. .upToFirstOccurrenceOf ("WebViewApi1_10$$", false, false);
  899. }
  900. else
  901. {
  902. if (static_cast<int> (androidMinimumSDK.get()) >= 23)
  903. {
  904. juceWebViewCodeNative << juceWebViewCodeAll.fromFirstOccurrenceOf ("$$WebViewNativeApi23", false, false)
  905. .upToFirstOccurrenceOf ("WebViewNativeApi23$$", false, false);
  906. juceWebViewCode << juceWebViewCodeAll.fromFirstOccurrenceOf ("$$WebViewApi23", false, false)
  907. .upToFirstOccurrenceOf ("WebViewApi23$$", false, false);
  908. }
  909. if (static_cast<int> (androidMinimumSDK.get()) >= 21)
  910. {
  911. juceWebViewCodeNative << juceWebViewCodeAll.fromFirstOccurrenceOf ("$$WebViewNativeApi21", false, false)
  912. .upToFirstOccurrenceOf ("WebViewNativeApi21$$", false, false);
  913. juceWebViewCode << juceWebViewCodeAll.fromFirstOccurrenceOf ("$$WebViewApi21", false, false)
  914. .upToFirstOccurrenceOf ("WebViewApi21$$", false, false);
  915. }
  916. else
  917. {
  918. juceWebViewCode << juceWebViewCodeAll.fromFirstOccurrenceOf ("$$WebViewApi11_20", false, false)
  919. .upToFirstOccurrenceOf ("WebViewApi11_20$$", false, false);
  920. }
  921. }
  922. return { juceWebViewImports, juceWebViewCodeNative, juceWebViewCode };
  923. }
  924. struct CameraCode
  925. {
  926. String imports;
  927. String main;
  928. };
  929. CameraCode getCameraCode (const File& javaSourceFolder) const
  930. {
  931. String juceCameraImports, juceCameraCode;
  932. if (static_cast<int> (androidMinimumSDK.get()) >= 21)
  933. {
  934. juceCameraImports << "import android.hardware.camera2.*;" << newLine;
  935. auto javaCameraFile = javaSourceFolder.getChildFile ("AndroidCamera.java");
  936. auto juceCameraCodeAll = javaCameraFile.loadFileAsString();
  937. juceCameraCode << juceCameraCodeAll.fromFirstOccurrenceOf ("$$CameraApi21", false, false)
  938. .upToFirstOccurrenceOf ("CameraApi21$$", false, false);
  939. }
  940. return { juceCameraImports, juceCameraCode };
  941. }
  942. struct VideoCode
  943. {
  944. String imports;
  945. String main;
  946. };
  947. VideoCode getVideoCode (const File& javaSourceFolder) const
  948. {
  949. String juceVideoImports, juceVideoCode;
  950. if (static_cast<int> (androidMinimumSDK.get()) >= 21)
  951. {
  952. juceVideoImports << "import android.database.ContentObserver;" << newLine;
  953. juceVideoImports << "import android.media.session.*;" << newLine;
  954. juceVideoImports << "import android.media.MediaMetadata;" << newLine;
  955. auto javaVideoFile = javaSourceFolder.getChildFile ("AndroidVideo.java");
  956. auto juceVideoCodeAll = javaVideoFile.loadFileAsString();
  957. juceVideoCode << juceVideoCodeAll.fromFirstOccurrenceOf ("$$VideoApi21", false, false)
  958. .upToFirstOccurrenceOf ("VideoApi21$$", false, false);
  959. }
  960. return { juceVideoImports, juceVideoCode };
  961. }
  962. void copyAdditionalJavaFiles (const File& sourceFolder, const File& targetFolder) const
  963. {
  964. auto inAppBillingJavaFileName = String ("IInAppBillingService.java");
  965. auto inAppBillingJavaSrcFile = sourceFolder.getChildFile (inAppBillingJavaFileName);
  966. auto inAppBillingJavaDestFile = targetFolder.getChildFile (inAppBillingJavaFileName);
  967. createDirectoryOrThrow (targetFolder);
  968. jassert (inAppBillingJavaSrcFile.existsAsFile());
  969. if (inAppBillingJavaSrcFile.existsAsFile())
  970. inAppBillingJavaSrcFile.copyFileTo (inAppBillingJavaDestFile);
  971. }
  972. void copyAdditionalJavaLibs (const File& targetFolder) const
  973. {
  974. auto libPaths = StringArray::fromLines (androidJavaLibs.get().toString());
  975. for (auto& p : libPaths)
  976. {
  977. File f = getTargetFolder().getChildFile (p);
  978. // Is the path to the java lib correct?
  979. jassert (f.existsAsFile());
  980. f.copyFileTo (targetFolder.getChildFile (f.getFileName()));
  981. }
  982. }
  983. void copyServicesJavaFiles (const File& javaSourceFolder, const File& targetFolder, const String& package) const
  984. {
  985. if (androidEnableRemoteNotifications.get())
  986. {
  987. String instanceIdFileName ("JuceFirebaseInstanceIdService.java");
  988. String messagingFileName ("JuceFirebaseMessagingService.java");
  989. File instanceIdFile (javaSourceFolder.getChildFile (instanceIdFileName));
  990. File messagingFile (javaSourceFolder.getChildFile (messagingFileName));
  991. jassert (instanceIdFile.existsAsFile());
  992. jassert (messagingFile .existsAsFile());
  993. Array<File> files;
  994. files.add (instanceIdFile);
  995. files.add (messagingFile);
  996. for (auto& file : files)
  997. {
  998. auto newContent = file.loadFileAsString()
  999. .replace ("package com.juce;", "package " + package + ";");
  1000. auto targetFile = targetFolder.getChildFile (file.getFileName());
  1001. overwriteFileIfDifferentOrThrow (targetFile, newContent);
  1002. }
  1003. }
  1004. }
  1005. void copyProviderJavaFile (const File& javaSourceFolder, const File& targetFolder, const String& package) const
  1006. {
  1007. auto providerFile = javaSourceFolder.getChildFile ("AndroidSharingContentProvider.java");
  1008. jassert (providerFile.existsAsFile());
  1009. auto targetFile = targetFolder.getChildFile ("SharingContentProvider.java");
  1010. auto fileContent = providerFile.loadFileAsString()
  1011. .replace ("package com.juce;", "package " + package + ";");
  1012. auto commonStart = fileContent.upToFirstOccurrenceOf ("$$ContentProviderApi11", false, false);
  1013. auto commonEnd = fileContent.fromFirstOccurrenceOf ("ContentProviderApi11$$", false, false);
  1014. auto middleContent = static_cast<int> (androidMinimumSDK.get()) >= 11
  1015. ? fileContent.fromFirstOccurrenceOf ("$$ContentProviderApi11", false, false)
  1016. .upToFirstOccurrenceOf ("ContentProviderApi11$$", false, false)
  1017. : String();
  1018. auto newContent = commonStart;
  1019. newContent << middleContent << commonEnd;
  1020. overwriteFileIfDifferentOrThrow (targetFile, newContent);
  1021. }
  1022. void copyExtraResourceFiles() const
  1023. {
  1024. for (ConstConfigIterator config (*this); config.next();)
  1025. {
  1026. auto& cfg = dynamic_cast<const AndroidBuildConfiguration&> (*config);
  1027. String cfgPath = cfg.isDebug() ? "app/src/debug" : "app/src/release";
  1028. String xmlValuesPath = cfg.isDebug() ? "app/src/debug/res/values" : "app/src/release/res/values";
  1029. String drawablesPath = cfg.isDebug() ? "app/src/debug/res" : "app/src/release/res";
  1030. String rawPath = cfg.isDebug() ? "app/src/debug/res/raw" : "app/src/release/res/raw";
  1031. copyExtraResourceFiles (cfg.getAdditionalXmlResources(), xmlValuesPath);
  1032. copyExtraResourceFiles (cfg.getAdditionalDrawableResources(), drawablesPath);
  1033. copyExtraResourceFiles (cfg.getAdditionalRawResources(), rawPath);
  1034. if (androidEnableRemoteNotifications.get())
  1035. {
  1036. auto remoteNotifsConfigFilePath = cfg.getRemoteNotifsConfigFile();
  1037. if (remoteNotifsConfigFilePath.isEmpty())
  1038. remoteNotifsConfigFilePath = androidRemoteNotificationsConfigFile.get().toString();
  1039. File file (getProject().getFile().getChildFile (remoteNotifsConfigFilePath));
  1040. // Settings file must be present for remote notifications to work and it must be called google-services.json.
  1041. jassert (file.existsAsFile() && file.getFileName() == "google-services.json");
  1042. copyExtraResourceFiles (remoteNotifsConfigFilePath, cfgPath);
  1043. }
  1044. }
  1045. }
  1046. void copyExtraResourceFiles (const String& resources, const String& dstRelativePath) const
  1047. {
  1048. auto resourcePaths = StringArray::fromTokens (resources, true);
  1049. auto parentFolder = getTargetFolder().getChildFile (dstRelativePath);
  1050. parentFolder.createDirectory();
  1051. for (auto& path : resourcePaths)
  1052. {
  1053. auto file = getProject().getFile().getChildFile (path);
  1054. jassert (file.exists());
  1055. if (file.exists())
  1056. file.copyFileTo (parentFolder.getChildFile (file.getFileName()));
  1057. }
  1058. }
  1059. String getActivityName() const
  1060. {
  1061. return androidActivityClass.get().toString().fromLastOccurrenceOf (".", false, false);
  1062. }
  1063. String getActivitySubClassName() const
  1064. {
  1065. auto activityPath = androidActivitySubClassName.get().toString();
  1066. return (activityPath.isEmpty()) ? getActivityName() : activityPath.fromLastOccurrenceOf (".", false, false);
  1067. }
  1068. String getActivityClassPackage() const
  1069. {
  1070. return androidActivityClass.get().toString().upToLastOccurrenceOf (".", false, false);
  1071. }
  1072. String getJNIActivityClassName() const
  1073. {
  1074. return androidActivityClass.get().toString().replaceCharacter ('.', '/');
  1075. }
  1076. static LibraryModule* getCoreModule (const OwnedArray<LibraryModule>& modules)
  1077. {
  1078. for (int i = modules.size(); --i >= 0;)
  1079. if (modules.getUnchecked (i)->getID() == "juce_core")
  1080. return modules.getUnchecked (i);
  1081. return nullptr;
  1082. }
  1083. //==============================================================================
  1084. String getNativeModuleBinaryName (const AndroidBuildConfiguration& config) const
  1085. {
  1086. return (isLibrary() ? File::createLegalFileName (config.getTargetBinaryNameString().trim()) : "juce_jni");
  1087. }
  1088. String getAppPlatform() const
  1089. {
  1090. auto ndkVersion = static_cast<int> (androidMinimumSDK.get());
  1091. if (ndkVersion == 9)
  1092. ndkVersion = 10; // (doesn't seem to be a version '9')
  1093. return "android-" + String (ndkVersion);
  1094. }
  1095. //==============================================================================
  1096. void writeStringsXML (const File& folder) const
  1097. {
  1098. for (ConstConfigIterator config (*this); config.next();)
  1099. {
  1100. auto& cfg = dynamic_cast<const AndroidBuildConfiguration&> (*config);
  1101. String customStringsXmlContent ("<resources>\n");
  1102. customStringsXmlContent << "<string name=\"app_name\">" << projectName << "</string>\n";
  1103. customStringsXmlContent << cfg.getCustomStringsXml();
  1104. customStringsXmlContent << "\n</resources>";
  1105. std::unique_ptr<XmlElement> strings (XmlDocument::parse (customStringsXmlContent));
  1106. String dir = cfg.isDebug() ? "debug" : "release";
  1107. String subPath = "app/src/" + dir + "/res/values/string.xml";
  1108. writeXmlOrThrow (*strings, folder.getChildFile (subPath), "utf-8", 100, true);
  1109. }
  1110. }
  1111. void writeAndroidManifest (const File& folder) const
  1112. {
  1113. std::unique_ptr<XmlElement> manifest (createManifestXML());
  1114. writeXmlOrThrow (*manifest, folder.getChildFile ("src/main/AndroidManifest.xml"), "utf-8", 100, true);
  1115. }
  1116. void writeIcon (const File& file, const Image& im) const
  1117. {
  1118. if (im.isValid())
  1119. {
  1120. createDirectoryOrThrow (file.getParentDirectory());
  1121. PNGImageFormat png;
  1122. MemoryOutputStream mo;
  1123. if (! png.writeImageToStream (im, mo))
  1124. throw SaveError ("Can't generate Android icon file");
  1125. overwriteFileIfDifferentOrThrow (file, mo);
  1126. }
  1127. }
  1128. void writeIcons (const File& folder) const
  1129. {
  1130. std::unique_ptr<Drawable> bigIcon (getBigIcon());
  1131. std::unique_ptr<Drawable> smallIcon (getSmallIcon());
  1132. if (bigIcon != nullptr && smallIcon != nullptr)
  1133. {
  1134. auto step = jmax (bigIcon->getWidth(), bigIcon->getHeight()) / 8;
  1135. writeIcon (folder.getChildFile ("drawable-xhdpi/icon.png"), getBestIconForSize (step * 8, false));
  1136. writeIcon (folder.getChildFile ("drawable-hdpi/icon.png"), getBestIconForSize (step * 6, false));
  1137. writeIcon (folder.getChildFile ("drawable-mdpi/icon.png"), getBestIconForSize (step * 4, false));
  1138. writeIcon (folder.getChildFile ("drawable-ldpi/icon.png"), getBestIconForSize (step * 3, false));
  1139. }
  1140. else if (auto* icon = (bigIcon != nullptr ? bigIcon.get() : smallIcon.get()))
  1141. {
  1142. writeIcon (folder.getChildFile ("drawable-mdpi/icon.png"), rescaleImageForIcon (*icon, icon->getWidth()));
  1143. }
  1144. }
  1145. void writeAppIcons (const File& folder) const
  1146. {
  1147. writeIcons (folder.getChildFile ("app/src/main/res/"));
  1148. }
  1149. static String sanitisePath (String path)
  1150. {
  1151. return expandHomeFolderToken (path).replace ("\\", "\\\\");
  1152. }
  1153. static String expandHomeFolderToken (const String& path)
  1154. {
  1155. auto homeFolder = File::getSpecialLocation (File::userHomeDirectory).getFullPathName();
  1156. return path.replace ("${user.home}", homeFolder)
  1157. .replace ("~", homeFolder);
  1158. }
  1159. //==============================================================================
  1160. void addCompileUnits (const Project::Item& projectItem, MemoryOutputStream& mo, Array<RelativePath>& excludeFromBuild) const
  1161. {
  1162. if (projectItem.isGroup())
  1163. {
  1164. for (int i = 0; i < projectItem.getNumChildren(); ++i)
  1165. addCompileUnits (projectItem.getChild(i), mo, excludeFromBuild);
  1166. }
  1167. else if (projectItem.shouldBeAddedToTargetProject())
  1168. {
  1169. RelativePath file (projectItem.getFile(), getTargetFolder().getChildFile ("app"), RelativePath::buildTargetFolder);
  1170. auto targetType = getProject().getTargetTypeFromFilePath (projectItem.getFile(), true);
  1171. mo << " \"" << file.toUnixStyle() << "\"" << newLine;
  1172. if ((! projectItem.shouldBeCompiled()) || (! shouldFileBeCompiledByDefault (file))
  1173. || (getProject().getProjectType().isAudioPlugin()
  1174. && targetType != ProjectType::Target::SharedCodeTarget
  1175. && targetType != ProjectType::Target::StandalonePlugIn))
  1176. excludeFromBuild.add (file);
  1177. }
  1178. }
  1179. void addCompileUnits (MemoryOutputStream& mo, Array<RelativePath>& excludeFromBuild) const
  1180. {
  1181. for (int i = 0; i < getAllGroups().size(); ++i)
  1182. addCompileUnits (getAllGroups().getReference(i), mo, excludeFromBuild);
  1183. }
  1184. //==============================================================================
  1185. StringArray getCmakeDefinitions() const
  1186. {
  1187. auto toolchain = gradleToolchain.get().toString();
  1188. bool isClang = (toolchain == "clang");
  1189. StringArray cmakeArgs;
  1190. cmakeArgs.add ("\"-DANDROID_TOOLCHAIN=" + toolchain + "\"");
  1191. cmakeArgs.add ("\"-DANDROID_PLATFORM=" + getAppPlatform() + "\"");
  1192. cmakeArgs.add (String ("\"-DANDROID_STL=") + (isClang ? "c++_static" : "gnustl_static") + "\"");
  1193. cmakeArgs.add ("\"-DANDROID_CPP_FEATURES=exceptions rtti\"");
  1194. cmakeArgs.add ("\"-DANDROID_ARM_MODE=arm\"");
  1195. cmakeArgs.add ("\"-DANDROID_ARM_NEON=TRUE\"");
  1196. return cmakeArgs;
  1197. }
  1198. //==============================================================================
  1199. StringArray getAndroidCompilerFlags() const
  1200. {
  1201. StringArray cFlags;
  1202. cFlags.add ("\"-fsigned-char\"");
  1203. return cFlags;
  1204. }
  1205. StringArray getAndroidCxxCompilerFlags() const
  1206. {
  1207. auto cxxFlags = getAndroidCompilerFlags();
  1208. auto cppStandard = project.getCppStandardString();
  1209. if (cppStandard == "latest" || cppStandard == "17") // C++17 flag isn't supported yet so use 1z for now
  1210. cppStandard = "1z";
  1211. cppStandard = "-std=" + String (shouldUseGNUExtensions() ? "gnu++" : "c++") + cppStandard;
  1212. cxxFlags.add (cppStandard.quoted());
  1213. return cxxFlags;
  1214. }
  1215. StringArray getProjectCompilerFlags() const
  1216. {
  1217. auto cFlags = getAndroidCompilerFlags();
  1218. cFlags.addArray (getEscapedFlags (StringArray::fromTokens (getExtraCompilerFlagsString(), true)));
  1219. return cFlags;
  1220. }
  1221. StringArray getProjectCxxCompilerFlags() const
  1222. {
  1223. auto cxxFlags = getAndroidCxxCompilerFlags();
  1224. cxxFlags.addArray (getEscapedFlags (StringArray::fromTokens (getExtraCompilerFlagsString(), true)));
  1225. return cxxFlags;
  1226. }
  1227. //==============================================================================
  1228. StringPairArray getAndroidPreprocessorDefs() const
  1229. {
  1230. StringPairArray defines;
  1231. defines.set ("JUCE_ANDROID", "1");
  1232. defines.set ("JUCE_ANDROID_API_VERSION", androidMinimumSDK.get());
  1233. defines.set ("JUCE_ANDROID_ACTIVITY_CLASSNAME", getJNIActivityClassName().replaceCharacter ('/', '_'));
  1234. defines.set ("JUCE_ANDROID_ACTIVITY_CLASSPATH", "\"" + getJNIActivityClassName() + "\"");
  1235. defines.set ("JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSNAME", getSharingContentProviderClassName().replaceCharacter('.', '_'));
  1236. defines.set ("JUCE_ANDROID_SHARING_CONTENT_PROVIDER_CLASSPATH", "\"" + getSharingContentProviderClassName().replaceCharacter('.', '/') + "\"");
  1237. defines.set ("JUCE_PUSH_NOTIFICATIONS", "1");
  1238. if (androidInAppBillingPermission.get())
  1239. defines.set ("JUCE_IN_APP_PURCHASES", "1");
  1240. if (androidEnableRemoteNotifications.get())
  1241. {
  1242. auto instanceIdClassName = getActivityClassPackage() + ".JuceFirebaseInstanceIdService";
  1243. auto messagingClassName = getActivityClassPackage() + ".JuceFirebaseMessagingService";
  1244. defines.set ("JUCE_FIREBASE_INSTANCE_ID_SERVICE_CLASSNAME", instanceIdClassName.replaceCharacter ('.', '_'));
  1245. defines.set ("JUCE_FIREBASE_MESSAGING_SERVICE_CLASSNAME", messagingClassName.replaceCharacter ('.', '_'));
  1246. }
  1247. if (supportsGLv3())
  1248. defines.set ("JUCE_ANDROID_GL_ES_VERSION_3_0", "1");
  1249. return defines;
  1250. }
  1251. String getSharingContentProviderClassName() const
  1252. {
  1253. return getActivityClassPackage() + ".SharingContentProvider";
  1254. }
  1255. StringPairArray getProjectPreprocessorDefs() const
  1256. {
  1257. auto defines = getAndroidPreprocessorDefs();
  1258. return mergePreprocessorDefs (defines, getAllPreprocessorDefs());
  1259. }
  1260. StringPairArray getConfigPreprocessorDefs (const BuildConfiguration& config) const
  1261. {
  1262. auto cfgDefines = config.getUniquePreprocessorDefs();
  1263. if (config.isDebug())
  1264. {
  1265. cfgDefines.set ("DEBUG", "1");
  1266. cfgDefines.set ("_DEBUG", "1");
  1267. }
  1268. else
  1269. {
  1270. cfgDefines.set ("NDEBUG", "1");
  1271. }
  1272. return cfgDefines;
  1273. }
  1274. //==============================================================================
  1275. StringArray getAndroidLibraries() const
  1276. {
  1277. StringArray libraries;
  1278. libraries.add ("log");
  1279. libraries.add ("android");
  1280. libraries.add (supportsGLv3() ? "GLESv3" : "GLESv2");
  1281. libraries.add ("EGL");
  1282. return libraries;
  1283. }
  1284. //==============================================================================
  1285. StringArray getHeaderSearchPaths (const BuildConfiguration& config) const
  1286. {
  1287. auto paths = extraSearchPaths;
  1288. paths.addArray (config.getHeaderSearchPaths());
  1289. paths = getCleanedStringArray (paths);
  1290. return paths;
  1291. }
  1292. //==============================================================================
  1293. String escapeDirectoryForCmake (const String& path) const
  1294. {
  1295. auto relative =
  1296. RelativePath (path, RelativePath::buildTargetFolder)
  1297. .rebased (getTargetFolder(), getTargetFolder().getChildFile ("app"), RelativePath::buildTargetFolder);
  1298. return relative.toUnixStyle();
  1299. }
  1300. void writeCmakePathLines (MemoryOutputStream& mo, const String& prefix, const String& firstLine, const StringArray& paths,
  1301. const String& suffix = ")") const
  1302. {
  1303. if (paths.size() > 0)
  1304. {
  1305. mo << prefix << firstLine << newLine;
  1306. for (auto& path : paths)
  1307. mo << prefix << " \"" << escapeDirectoryForCmake (path) << "\"" << newLine;
  1308. mo << prefix << suffix << newLine << newLine;
  1309. }
  1310. }
  1311. static StringArray getEscapedPreprocessorDefs (const StringPairArray& defs)
  1312. {
  1313. StringArray escapedDefs;
  1314. for (int i = 0; i < defs.size(); ++i)
  1315. {
  1316. auto escaped = "\"-D" + defs.getAllKeys()[i];
  1317. auto value = defs.getAllValues()[i];
  1318. if (value.isNotEmpty())
  1319. {
  1320. value = value.replace ("\"", "\\\"");
  1321. if (value.containsChar (L' '))
  1322. value = "\\\"" + value + "\\\"";
  1323. escaped += ("=" + value);
  1324. }
  1325. escapedDefs.add (escaped + "\"");
  1326. }
  1327. return escapedDefs;
  1328. }
  1329. static StringArray getEscapedFlags (const StringArray& flags)
  1330. {
  1331. StringArray escaped;
  1332. for (auto& flag : flags)
  1333. escaped.add ("\"" + flag + "\"");
  1334. return escaped;
  1335. }
  1336. //==============================================================================
  1337. XmlElement* createManifestXML() const
  1338. {
  1339. auto* manifest = createManifestElement();
  1340. createSupportsScreensElement (*manifest);
  1341. createUsesSdkElement (*manifest);
  1342. createPermissionElements (*manifest);
  1343. createOpenGlFeatureElement (*manifest);
  1344. if (! isLibrary())
  1345. {
  1346. auto* app = createApplicationElement (*manifest);
  1347. auto* act = createActivityElement (*app);
  1348. createIntentElement (*act);
  1349. createServiceElements (*app);
  1350. createProviderElement (*app);
  1351. }
  1352. return manifest;
  1353. }
  1354. XmlElement* createManifestElement() const
  1355. {
  1356. auto* manifest = XmlDocument::parse (androidManifestCustomXmlElements.get());
  1357. if (manifest == nullptr)
  1358. manifest = new XmlElement ("manifest");
  1359. setAttributeIfNotPresent (*manifest, "xmlns:android", "http://schemas.android.com/apk/res/android");
  1360. setAttributeIfNotPresent (*manifest, "android:versionCode", androidVersionCode.get());
  1361. setAttributeIfNotPresent (*manifest, "android:versionName", project.getVersionString());
  1362. setAttributeIfNotPresent (*manifest, "package", getActivityClassPackage());
  1363. return manifest;
  1364. }
  1365. void createSupportsScreensElement (XmlElement& manifest) const
  1366. {
  1367. if (! isLibrary())
  1368. {
  1369. if (manifest.getChildByName ("supports-screens") == nullptr)
  1370. {
  1371. auto* screens = manifest.createNewChildElement ("supports-screens");
  1372. screens->setAttribute ("android:smallScreens", "true");
  1373. screens->setAttribute ("android:normalScreens", "true");
  1374. screens->setAttribute ("android:largeScreens", "true");
  1375. screens->setAttribute ("android:anyDensity", "true");
  1376. }
  1377. }
  1378. }
  1379. void createUsesSdkElement (XmlElement& manifest) const
  1380. {
  1381. auto* sdk = getOrCreateChildWithName (manifest, "uses-sdk");
  1382. setAttributeIfNotPresent (*sdk, "android:minSdkVersion", androidMinimumSDK.get());
  1383. setAttributeIfNotPresent (*sdk, "android:targetSdkVersion", androidMinimumSDK.get());
  1384. }
  1385. void createPermissionElements (XmlElement& manifest) const
  1386. {
  1387. auto permissions = getPermissionsRequired();
  1388. forEachXmlChildElementWithTagName (manifest, child, "uses-permission")
  1389. {
  1390. permissions.removeString (child->getStringAttribute ("android:name"), false);
  1391. }
  1392. for (int i = permissions.size(); --i >= 0;)
  1393. manifest.createNewChildElement ("uses-permission")->setAttribute ("android:name", permissions[i]);
  1394. }
  1395. void createOpenGlFeatureElement (XmlElement& manifest) const
  1396. {
  1397. if (project.getModules().isModuleEnabled ("juce_opengl"))
  1398. {
  1399. XmlElement* glVersion = nullptr;
  1400. forEachXmlChildElementWithTagName (manifest, child, "uses-feature")
  1401. {
  1402. if (child->getStringAttribute ("android:glEsVersion").isNotEmpty())
  1403. {
  1404. glVersion = child;
  1405. break;
  1406. }
  1407. }
  1408. if (glVersion == nullptr)
  1409. glVersion = manifest.createNewChildElement ("uses-feature");
  1410. setAttributeIfNotPresent (*glVersion, "android:glEsVersion", (static_cast<int> (androidMinimumSDK.get()) >= 18 ? "0x00030000" : "0x00020000"));
  1411. setAttributeIfNotPresent (*glVersion, "android:required", "true");
  1412. }
  1413. }
  1414. XmlElement* createApplicationElement (XmlElement& manifest) const
  1415. {
  1416. auto* app = getOrCreateChildWithName (manifest, "application");
  1417. setAttributeIfNotPresent (*app, "android:label", "@string/app_name");
  1418. if (androidTheme.get().toString().isNotEmpty())
  1419. setAttributeIfNotPresent (*app, "android:theme", androidTheme.get());
  1420. if (! app->hasAttribute ("android:icon"))
  1421. {
  1422. std::unique_ptr<Drawable> bigIcon (getBigIcon()), smallIcon (getSmallIcon());
  1423. if (bigIcon != nullptr || smallIcon != nullptr)
  1424. app->setAttribute ("android:icon", "@drawable/icon");
  1425. }
  1426. if (static_cast<int> (androidMinimumSDK.get()) >= 11)
  1427. {
  1428. if (! app->hasAttribute ("android:hardwareAccelerated"))
  1429. app->setAttribute ("android:hardwareAccelerated", "false"); // (using the 2D acceleration slows down openGL)
  1430. }
  1431. else
  1432. {
  1433. app->removeAttribute ("android:hardwareAccelerated");
  1434. }
  1435. return app;
  1436. }
  1437. XmlElement* createActivityElement (XmlElement& application) const
  1438. {
  1439. auto* act = getOrCreateChildWithName (application, "activity");
  1440. setAttributeIfNotPresent (*act, "android:name", getActivitySubClassName());
  1441. setAttributeIfNotPresent (*act, "android:label", "@string/app_name");
  1442. if (! act->hasAttribute ("android:configChanges"))
  1443. {
  1444. String configChanges ("keyboardHidden|orientation");
  1445. if (static_cast<int> (androidMinimumSDK.get()) >= 13)
  1446. configChanges += "|screenSize";
  1447. act->setAttribute ("android:configChanges", configChanges);
  1448. }
  1449. else
  1450. {
  1451. auto configChanges = act->getStringAttribute ("android:configChanges");
  1452. if (static_cast<int> (androidMinimumSDK.get()) < 13 && configChanges.contains ("screenSize"))
  1453. {
  1454. configChanges = configChanges.replace ("|screenSize", "")
  1455. .replace ("screenSize|", "")
  1456. .replace ("screenSize", "");
  1457. act->setAttribute ("android:configChanges", configChanges);
  1458. }
  1459. }
  1460. if (androidScreenOrientation.get() == "landscape")
  1461. {
  1462. String landscapeString = static_cast<int> (androidMinimumSDK.get()) < 9
  1463. ? "landscape"
  1464. : (static_cast<int> (androidMinimumSDK.get()) < 18 ? "sensorLandscape" : "userLandscape");
  1465. setAttributeIfNotPresent (*act, "android:screenOrientation", landscapeString);
  1466. }
  1467. else
  1468. {
  1469. setAttributeIfNotPresent (*act, "android:screenOrientation", androidScreenOrientation.get());
  1470. }
  1471. setAttributeIfNotPresent (*act, "android:launchMode", "singleTask");
  1472. // Using the 2D acceleration slows down OpenGL. We *do* enable it here for the activity though, and we disable it
  1473. // in each ComponentPeerView instead. This way any embedded native views, which are not children of ComponentPeerView,
  1474. // can still use hardware acceleration if needed (e.g. web view).
  1475. if (static_cast<int> (androidMinimumSDK.get()) >= 11)
  1476. {
  1477. if (! act->hasAttribute ("android:hardwareAccelerated"))
  1478. act->setAttribute ("android:hardwareAccelerated", "true"); // (using the 2D acceleration slows down openGL)
  1479. }
  1480. else
  1481. {
  1482. act->removeAttribute ("android:hardwareAccelerated");
  1483. }
  1484. return act;
  1485. }
  1486. void createIntentElement (XmlElement& application) const
  1487. {
  1488. auto* intent = getOrCreateChildWithName (application, "intent-filter");
  1489. auto* action = getOrCreateChildWithName (*intent, "action");
  1490. setAttributeIfNotPresent (*action, "android:name", "android.intent.action.MAIN");
  1491. auto* category = getOrCreateChildWithName (*intent, "category");
  1492. setAttributeIfNotPresent (*category, "android:name", "android.intent.category.LAUNCHER");
  1493. }
  1494. void createServiceElements (XmlElement& application) const
  1495. {
  1496. if (androidEnableRemoteNotifications.get())
  1497. {
  1498. auto* service = application.createNewChildElement ("service");
  1499. service->setAttribute ("android:name", ".JuceFirebaseMessagingService");
  1500. auto* intentFilter = service->createNewChildElement ("intent-filter");
  1501. intentFilter->createNewChildElement ("action")->setAttribute ("android:name", "com.google.firebase.MESSAGING_EVENT");
  1502. service = application.createNewChildElement ("service");
  1503. service->setAttribute ("android:name", ".JuceFirebaseInstanceIdService");
  1504. intentFilter = service->createNewChildElement ("intent-filter");
  1505. intentFilter->createNewChildElement ("action")->setAttribute ("android:name", "com.google.firebase.INSTANCE_ID_EVENT");
  1506. auto* metaData = application.createNewChildElement ("meta-data");
  1507. metaData->setAttribute ("android:name", "firebase_analytics_collection_deactivated");
  1508. metaData->setAttribute ("android:value", "true");
  1509. }
  1510. }
  1511. void createProviderElement (XmlElement& application) const
  1512. {
  1513. if (androidEnableContentSharing.get())
  1514. {
  1515. auto* provider = application.createNewChildElement ("provider");
  1516. provider->setAttribute ("android:name", getSharingContentProviderClassName());
  1517. provider->setAttribute ("android:authorities", getSharingContentProviderClassName().toLowerCase());
  1518. provider->setAttribute ("android:grantUriPermissions", "true");
  1519. provider->setAttribute ("android:exported", "false");
  1520. }
  1521. }
  1522. static XmlElement* getOrCreateChildWithName (XmlElement& element, const String& name)
  1523. {
  1524. auto* child = element.getChildByName (name);
  1525. if (child == nullptr)
  1526. child = element.createNewChildElement (name);
  1527. return child;
  1528. }
  1529. static void setAttributeIfNotPresent (XmlElement& element, const Identifier& attribute, const String& value)
  1530. {
  1531. if (! element.hasAttribute (attribute.toString()))
  1532. element.setAttribute (attribute, value);
  1533. }
  1534. StringArray getPermissionsRequired() const
  1535. {
  1536. StringArray s = StringArray::fromTokens (androidOtherPermissions.get().toString(), ", ", {});
  1537. if (androidInternetNeeded.get())
  1538. s.add ("android.permission.INTERNET");
  1539. if (androidMicNeeded.get())
  1540. s.add ("android.permission.RECORD_AUDIO");
  1541. if (androidCameraNeeded.get())
  1542. s.add ("android.permission.CAMERA");
  1543. if (androidBluetoothNeeded.get())
  1544. {
  1545. s.add ("android.permission.BLUETOOTH");
  1546. s.add ("android.permission.BLUETOOTH_ADMIN");
  1547. s.add ("android.permission.ACCESS_COARSE_LOCATION");
  1548. }
  1549. if (androidExternalReadPermission.get())
  1550. s.add ("android.permission.READ_EXTERNAL_STORAGE");
  1551. if (androidExternalWritePermission.get())
  1552. s.add ("android.permission.WRITE_EXTERNAL_STORAGE");
  1553. if (androidInAppBillingPermission.get())
  1554. s.add ("com.android.vending.BILLING");
  1555. if (androidVibratePermission.get())
  1556. s.add ("android.permission.VIBRATE");
  1557. return getCleanedStringArray (s);
  1558. }
  1559. //==============================================================================
  1560. bool isLibrary() const
  1561. {
  1562. return getProject().getProjectType().isDynamicLibrary()
  1563. || getProject().getProjectType().isStaticLibrary();
  1564. }
  1565. static String toGradleList (const StringArray& array)
  1566. {
  1567. StringArray escapedArray;
  1568. for (auto element : array)
  1569. escapedArray.add ("\"" + element.replace ("\\", "\\\\").replace ("\"", "\\\"") + "\"");
  1570. return escapedArray.joinIntoString (", ");
  1571. }
  1572. bool supportsGLv3() const
  1573. {
  1574. return (static_cast<int> (androidMinimumSDK.get()) >= 18);
  1575. }
  1576. //==============================================================================
  1577. Value sdkPath, ndkPath;
  1578. const File AndroidExecutable;
  1579. JUCE_DECLARE_NON_COPYABLE (AndroidProjectExporter)
  1580. };
  1581. inline ProjectExporter* createAndroidExporter (Project& p, const ValueTree& t)
  1582. {
  1583. return new AndroidProjectExporter (p, t);
  1584. }
  1585. inline ProjectExporter* createAndroidExporterForSetting (Project& p, const ValueTree& t)
  1586. {
  1587. return AndroidProjectExporter::createForSettings (p, t);
  1588. }